/* * Command processor for NRO text processor * * Originally by Stephen L. Browning, 5723 North Parker Avenue * Indianapolis, Indiana 46220 * * Transformed beyond immediate recognition, and * adapted for Amiga by Olaf Seibert, KosmoSoft * * Vossendijk 149-1 (study) Beek 5 (home) * 5634 TN Nijmegen 5815 CS Merselo * The Netherlands The Netherlands * Phone: * (...-31)80561045 (...-31)4786205 * or 080-561045 04786-205 * * This program is NOT in the public domain. It may, however * be distributed only at no charge, and this notice must be * included unaltered. */ #include #include "nro.h" #include "nroxtrn.h" uchar *skipbl(); uchar *skipwd(); /* * Communicating the current file through a global * variable is a bit of a kludge. We could as well use * it everywhere instead of passing it as a parameter * all the time. But that would not be very `structured', * whatever that may be. */ comand(word) uchar *word; { int ct, val; int spval; uchar argtyp, chr; uchar *macexp; val = getcmdwrd(word, infile); if (word[0] == env.c2chr) { env.dontbrk = TRUE; word++; } else if (word[0] == env.cmdchr) { word++; } else if (val > 0) { putbak(' '); pbstr(word); return; } ct = comtyp(word, &macexp); if (ct == UNKNOWN) { error("nro: unrecognized command %s\n", word); env.dontbrk = FALSE; return; } if (! (ct & NOARGS)) val = getval(&argtyp, infile); /* Eat more of line */ switch (ct & ~NOARGS) { case BO: /* Bold face */ set(&env.boval, val, argtyp, 1, -1, HUGE); if (env.boval) env.reqmode |= FXBO; else env.reqmode &= ~FXBO; if (dc.bsflg != BSAMIGA) env.cuval = env.ulval = 0; break; case BP: /* Begin page */ if (pg.lineno > 0) space(pg.plval); set(&pg.curpag, val, argtyp, pg.curpag+1, -HUGE, HUGE); pg.newpag = pg.curpag; break; case BR: /* Break */ dobrk(); break; case BS: /* Backspaces in output: 0=No, Amiga; 1=Yes; 2=Use CR */ set(&dc.bsflg, val, argtyp, 1, 0, 2); break; case C2: /* No-break command character */ while ((chr = ngetc(infile)) == ' ' || chr == '\t'); if (iseol(chr)) env.c2chr = C2CHAR; else { env.c2chr = chr; ct = 0; } break; case CC: /* Command character */ while ((chr = ngetc(infile)) == ' ' || chr == '\t'); if (iseol(chr)) env.cmdchr = CMDCHAR; else { env.cmdchr = chr; ct = 0; } break; case CE: /* Center */ dobrk(); set(&env.ceval, val, argtyp, 1, -1, HUGE); break; case COMMENT: while (chr = ngetc(infile), isnteol(chr)); break; case CU: /* Continuous underline */ set(&env.cuval, val, argtyp, 1, -1, HUGE); if (env.cuval) env.reqmode |= FXUL; else env.reqmode &= ~FXUL; if (dc.bsflg != BSAMIGA) env.ulval = env.boval = 0; break; case DE: /* Define macro */ defmac(word, infile); break; case EF: /* Even footer */ gettl(infile, pg.efoot, NULL, &pg.eflim[0], NULL); break; case EH: /* Even header */ gettl(infile, pg.ehead, NULL, &pg.ehlim[0], NULL); break; case EL: /* Else */ doel(infile); break; case EN: /* End macro definition */ error("nro: missing .de command\n"); break; case EV: /* Environment switch */ if (isdigit(argtyp)) { /* Supplied argument: push */ if (dc.envsp >= ENVSTACK-1) { error("nro: cannot push environment.\n"); break; } spval = dc.envstack[dc.envsp]; /* Current environment */ dc.envstack[++dc.envsp] = val; /* Save number on stack*/ storenv(spval); /* Save current envir. */ loadenv(val); /* Get new one */ } else { /* Pop an environment */ if ((int) dc.envsp <= 0) { error("*** nro: cannot pop environment.\n"); break; } spval = dc.envstack[dc.envsp]; /* Current environment */ val = dc.envstack[--dc.envsp]; /* Number of old env */ if (argtyp == '-') freenv(spval);/* Dump current environ*/ else storenv(spval); /* ..or save it */ loadenv(val); /* Get old one back */ } break; case FI: /* Fill */ dobrk(); env.fill = YES; break; case FO: /* Footer */ gettl(infile, pg.efoot, pg.ofoot, &pg.eflim[0], &pg.oflim[0]); break; case HE: /* Header */ gettl(infile, pg.ehead, pg.ohead, &pg.ehlim[0], &pg.ohlim[0]); break; case IE: case IF: doieif(infile, ct & ~NOARGS); break; case IN: /* Indenting */ dobrk(); set(&env.inval, val, argtyp, 0, 0, env.tmval-1); env.tival = env.inval; break; case IT: /* Italic face */ set(&env.itval, val, argtyp, 1, -1, HUGE); if (env.itval) env.reqmode |= FXIT; else env.reqmode &= ~FXIT; if (dc.bsflg != BSAMIGA) error("nro: italics cannot be done by overstrike :-)\n"); break; case JU: /* Justify */ env.juval = YES; break; case LS: /* Line spacing */ set(&env.lsval, val, argtyp, 1, 1, HUGE); break; case M1: /* Set topmost margin */ set(&pg.m1val, val, argtyp, 2, 0, HUGE); break; case M2: /* Set second top margin */ set(&pg.m2val, val, argtyp, 2, 0, HUGE); break; case M3: /* Set first bottom margin */ set(&pg.m3val, val, argtyp, 2, 0, HUGE); pg.bottom = pg.plval - pg.m4val - pg.m3val; break; case M4: /* Set bottom-most margin */ set(&pg.m4val, val, argtyp, 2, 0, HUGE); pg.bottom = pg.plval - pg.m4val - pg.m3val; break; case MACRO: /* Macro expansion */ maceval(macexp, infile); break; case NE: /* Need n lines */ /* dobrk(); */ if ((pg.bottom-pg.lineno+1) < (val*env.lsval)) space(pg.plval); break; case NF: /* No fill */ dobrk(); env.fill = NO; break; case NJ: /* No justify */ env.juval = NO; break; case NR: /* Set number register */ while ((chr = ngetc(infile)) == ' ' || chr == '\t'); if (!isalpha(chr)) { error("nro: invalid or missing number register name\n"); } else { ct = tolower(chr) - 'a'; val = getval(&chr, infile); set(&dc.nr[ct], val, chr, 0, -HUGE, HUGE); } ct = 0; break; case OF: /* Odd footer */ gettl(infile, pg.ofoot, NULL, &pg.oflim[0], NULL); break; case OH: /* Odd header */ gettl(infile, pg.ohead, NULL, &pg.ohlim[0], NULL); break; case PC: /* Page number character */ while ((chr = ngetc(infile)) == ' ' || chr == '\t'); if (iseol(chr)) env.pgchr = EOS; else { env.pgchr = chr; ct = 0; } break; case PL: /* Page length */ set(&pg.plval, val, argtyp, PAGELEN, pg.m1val+pg.m2val+pg.m3val+pg.m4val+1, HUGE); pg.bottom = pg.plval - pg.m3val - pg.m4val; break; case PN: /* Page numbering mode */ set(&env.pnflg, val, argtyp, PNARABIC, PNARABIC, PNUROMAN); break; case PO: /* Page offset */ set(&pg.offset, val, argtyp, 0, 0, HUGE); break; case RM: /* Right margin */ set(&env.rmval, val, argtyp, PAGEWIDTH, env.tival+1, HUGE); env.tmval = env.rmval; break; case SO: /* Source file */ if (getcmdwrd(word, infile) == EOF) break; skipeol(infile); ct = NOARGS; if (dc.flevel+1 >= NFILES) { error("nro: .so commands nested too deeply\n"); break; } if ((sofile[dc.flevel+1] = fopen(word, "r")) == NULL) { error("nro: unable to open %s\n", word); break; } if (verbose > 2) error("nro: processing file '%s'\n", word); sopbb[dc.flevel] = mac.pbb; /* Stack push back stacks */ mac.pbb = mac.ppb + 1; dc.flevel++; infile = sofile[dc.flevel]; break; case SP: /* Space */ set(&spval, val, argtyp, 1, 0, HUGE); space(spval * env.lsval); break; case TA: /* Tab settings */ tabs(infile); break; case TI: /* Temporary indent */ dobrk(); set(&env.tival, val, argtyp, 0, 0, env.tmval); break; case TM: /* Terminal Message */ fflush(stdout); if (env.dontbrk == FALSE || pout != stdout) { while (val = ngetc(infile), isnteol(val) && val != EOF) putc(val, stderr); putc('\n', stderr); fflush(stderr); } else ct = 0; /* Skip rest of command line */ break; case UL: /* Underline */ set(&env.ulval, val, argtyp, -1, 1, HUGE); if (env.ulval) env.reqmode |= FXUL; else env.reqmode &= ~FXUL; if (dc.bsflg != BSAMIGA) env.cuval = env.boval = 0; break; case UN: /* Undefine macro */ undefmac(word, infile); break; } env.dontbrk = FALSE; if (argtyp != STRINGTYP && !(ct & NOARGS)) skipeol(infile); return; } /* * Decodes nro command and returns its associated * value. */ comtyp(line, macdef) uchar *line; uchar **macdef; { uchar c1, c2; /* * First check to see if the command is a macro. * If it is, return expansion in macdef. * Note that upper and lower case characters are handled * differently for macro names, but not for normal command names. */ if ((*macdef = getmac(line)) != NULL) { return MACRO | NOARGS; } c1 = tolower(*line++); c2 = tolower(*line ); switch (c1) { case '"': case '*': return COMMENT | NOARGS; case 'b': if (c2 == 'o') return BO; else if (c2 == 'p') return BP; else if (c2 == 'r') return BR; else if (c2 == 's') return BS; break; case 'c': if (c2 == '2') return C2 | NOARGS; else if (c2 == 'c') return CC | NOARGS; else if (c2 == 'e') return CE; else if (c2 == 'u') return CU; break; case 'd': if (c2 == 'e') return DE | NOARGS; break; case 'e': if (c2 == 'f') return EF | NOARGS; else if (c2 == 'h') return EH | NOARGS; else if (c2 == 'l') return EL | NOARGS; else if (c2 == 'n') return EN; else if (c2 == 'v') return EV; break; case 'f': if (c2 == 'i') return FI; else if (c2 == 'o') return FO | NOARGS; break; case 'h': if (c2 == 'e') return HE | NOARGS; break; case 'i': if (c2 == 'e') return IE | NOARGS; else if (c2 == 'f') return IF | NOARGS; else if (c2 == 'n') return IN; else if (c2 == 't') return IT; break; case 'j': if (c2 == 'u') return JU; break; case 'l': if (c2 == 's') return LS; break; case 'm': if (c2 == '1') return M1; else if (c2 == '2') return M2; else if (c2 == '3') return M3; else if (c2 == '4') return M4; break; case 'n': if (c2 == 'e') return NE; else if (c2 == 'f') return NF; else if (c2 == 'j') return NJ; else if (c2 == 'r') return NR | NOARGS; break; case 'o': if (c2 == 'f') return OF | NOARGS; else if (c2 == 'h') return OH | NOARGS; break; case 'p': if (c2 == 'c') return PC | NOARGS; else if (c2 == 'l') return PL; else if (c2 == 'n') return PN; else if (c2 == 'o') return PO; break; case 'r': if (c2 == 'm') return RM; break; case 's': if (c2 == 'o') return SO | NOARGS; else if (c2 == 'p') return SP; break; case 't': if (c2 == 'a') return TA | NOARGS; else if (c2 == 'i') return TI; else if (c2 == 'm') return TM | NOARGS; break; case 'u': if (c2 == 'l') return UL; else if (c2 == 'n') return UN | NOARGS; } return UNKNOWN; } /* * Retrieves optional argument following nro command. * returns positive integer value with sign (if any) * saved in character addressed by pargtyp. * In case of a string, puts in a quote. * It won't read the character it doesn't understand, * i.e. semicolons and newlines, and garbage. */ getval(pargtyp, infile) uchar *pargtyp; register FILE *infile; { register uchar chr, operator = '+'; short digit; register int val, cumval; while ((chr = ngetc(infile)) == ' ' || chr == '\t'); *pargtyp = chr; if (chr == '+' || chr == '-' || chr == '/' || chr == '*') chr = ngetc(infile); else if (chr == STRINGTYP) return 1; cumval = 0; again: val = 0; if (chr == '(') { /* Parenthesized subexpression */ val = getval(&digit, infile); /* Dummy argtype pointer */ chr = ngetc(infile); if (chr != ')') error("nro: missing close parenthesis in expression\n"); else chr = ngetc(infile); /* Get next operator or anything */ if (*pargtyp == '(') *pargtyp = '0'; } else { /* Try to collect a number from the input */ while ((digit = atod(chr)) >= 0) { val = 10 * val + digit; chr = ngetc(infile); } } /* Check if we need to evaluate an operator */ if (operator) { switch (operator) { case '+': cumval += val; break; case '-': cumval -= val; break; case '*': cumval *= val; break; case '/': if (val != 0) cumval /= val; else error("nro: division by zero\n"); break; case '%': if (val != 0) cumval %= val; else error("nro: modulo by zero\n"); break; case '<': cumval = cumval < val; break; case '=': cumval = cumval == val; break; case '>': cumval = cumval > val; break; case '&': cumval = cumval & val; break; case '|': cumval = cumval | val; break; } operator = '\0'; } /* See if there is more to come */ switch (chr) { case '+': case '-': case '*': case '/': case '%': case '<': case '=': case '>': case '&': case '|': operator = chr; chr = ngetc(infile); goto again; default: putbak(chr); /* Put back what we can't interpret */ return cumval; } } /* * Convert string to decimal. * processes only positive values. */ ctod(p) uchar *p; { int val, d; val = 0; while (*p != EOS) { d = atod(*p++); if (d == -1) return val; val = 10 * val + d; } return val; } /* * Convert ascii character to decimal. */ atod(c) uchar c; { return ((c < '0') || (c > '9')) ? -1 : c-'0'; } /* * Get non-blank word from the input file. * Returns the number of spaces skipped before the word, * or EOF on end of file. * It won't read past a newline or semicolon, * but will skip the first space following the word. */ getcmdwrd(to, infile) register uchar *to; register FILE *infile; { register short chr; int skipped = 0; short length = 0; chr = ngetc(infile); if (chr == EOF) { *to = EOS; return EOF; } /* Skip spaces */ while (isspace(chr)) { chr = ngetc(infile); skipped++; } while (isntspace(chr) && isnteol(chr) && chr != EOF && chr != MORETXT && length < MAXWORD-3) { *to++ = chr; length++; chr = ngetc(infile); } if (iseol(chr) || chr == MORETXT) putbak(chr); *to = EOS; if (length >= MAXWORD-3) error("nro: command word buffer overflow\n"); return skipped; } /* * Skip the rest of the current input line */ skipeol(infile) FILE *infile; { int chr; while ((chr = ngetc(infile)) != '\n' && chr != MORETXT && chr != EOF); } /* * Process an IF or IE request */ doieif(infile, request) FILE *infile; int request; { uchar *strp; short negation; int chr, val; uchar delim; uchar string[MAXWORD]; while ((chr = ngetc(infile)) == ' ' || chr == '\t'); if (chr == '!') { negation = TRUE; chr = ngetc(infile); } else negation = FALSE; switch (chr) { case 'e': /* Even page number */ val = (pg.curpag % 2) == 0; break; case 'o': /* Odd page number */ val = (pg.curpag % 2) != 0; break; case 'n': /* Nro(ff) is formatter */ val = TRUE; break; case 't': /* Troff certainly not */ val = FALSE; break; case '0': case '1': case '2': case '3': case '4': case '5': case '6': case '7': case '8': case '9': case '+': case '-': case '(': /* Must be an expression */ putbak(chr); chr = getval(&string[0], infile); val = 0; set(&val, chr, string[0], 0, -HUGE, HUGE); val = val > 0; break; default: /* String comparison */ delim = chr; strp = string; /* Collect first string */ while ((chr = ngetc(infile)) != delim && strp < string + sizeof(string) - 2) *strp++ = chr; *strp = EOS; val = TRUE; strp = string; /* Compare with second string */ while ((chr = ngetc(infile)) != delim) { if (*strp++ != chr) val = FALSE; } if (*strp != EOS) val = FALSE; break; } if (negation) val = !val; if (request == IE) env.lastie = val; while ((chr = ngetc(infile)) == ' ' || chr == '\t'); if (val) { /* Condition is true. Don't skip any text */ if (chr != BEGIF) putbak(chr); } else { /* Need to skip some text, maybe even very much */ if (chr == BEGIF) /* Skip until end of line */ dc.iflvl = 1; while (isnteol(chr)) chr = ngetc(infile); } } /* * Process an EL request */ doel(infile) FILE *infile; { int chr, val; while ((chr = ngetc(infile)) == ' ' || chr == '\t'); /* Toggle last remembered condition */ val = env.lastie = !env.lastie; if (val) { /* Condition is true. Don't skip any text */ if (chr != BEGIF) putbak(chr); } else { /* Need to skip some text, maybe even very much */ if (chr == BEGIF) /* Skip until end of line */ dc.iflvl = 1; while (isnteol(chr)) chr = ngetc(infile); } } /* * Process a TA request */ tabs(infile) FILE *infile; { int chr, val, i, j; while ((chr = ngetc(infile)) == ' ' || chr == '\t'); if (iseol(chr) || chr == EOF) { /* No arguments */ for (i=0; i i; j--) env.tabstop[j] = env.tabstop[j-1]; env.tabstop[i] = val; } i = val; while ((chr = ngetc(infile)) == ' ' || chr == '\t'); if (iseol(chr) || chr == MORETXT || chr == EOF) break; } } /* * Loadenv - get an environment */ loadenv(number) int number; { struct environ *envp; short freeit = env.dontbrk; if (number < 0 || number >= NUMENV) return ERR; envp = environ[number]; if (envp) { /* It't there. Copy it. */ env = *envp; if (freeit) { /* We may free the saved image of it, */ freenv(number); /* if we are tight on memory. */ } } else { /* It isn't. Just use default values. */ initenv(); } return OK; } /* * Storenv - save an environment */ storenv(number) int number; { struct environ *envp; if (number < 0 || number >= NUMENV) return ERR; envp = environ[number]; if (envp == NULL) { /* Never saw this guy before */ if ((envp = (struct environ *)malloc(sizeof(*envp))) == NULL) { error("*** nro: cannot allocate environment #%d\n", number); return ERR; } else if (verbose > 2) error("nro: allocated environment #%d\n", number); environ[number] = envp; } *envp = env; return OK; } /* * Freenv - throw an environment away */ freenv(number) int number; { struct environ *envp; if (number < 0 || number >= NUMENV) return ERR; envp = environ[number]; if (envp) { free(envp); environ[number] = NULL; if (verbose > 2) error("nro: freed environment #%d\n", number); } return OK; } /* * End current filled line */ dobrk() { if (!env.dontbrk) { /* Breaks may be disabled by using .'xx */ if (env.outp > 0) { #ifdef CPM env.outbuf[env.outp++] = '\r'; env.outbuf[env.outp++] = '\n'; env.outbuf[env.outp ] = EOS; #else CPM env.outbuf[env.outp++] = '\n'; env.outbuf[env.outp ] = EOS; #endif CPM if (env.ceval != 0) /* Centering */ center(env.outbuf); put(env.outbuf); } env.outp = 0; env.outw = 0; env.outwds = 0; } } /* * Define a macro */ defmac(word, infile) uchar *word; FILE *infile; { uchar line[MAXLINE]; getcmdwrd(word, infile); skipeol(infile); if (word[0] == EOS && verbose) error("nro: appending to macro definition\n"); while (getlin(line, infile) != EOF) { if (line[0] == env.cmdchr && line[1] == 'e' && line[2] == 'n') break; if (putmac(word, line) == ERR) { error("*** nro: macro definition table full\n"); } word[0] = EOS; } } /* * Put macro definition into table. * If name == "", concatenate this definition to the previous one. */ putmac(name, def) uchar *name; uchar *def; { int lenofname, lenofdef; lenofname = strlen(name); lenofdef = strlen(def); if ((lenofname && mac.lastp >= mac.mxmdef) || (mac.emb + lenofname + lenofdef + 2 > &mac.mb[mac.macbuf])) { return ERR; } if (lenofname) { ++mac.lastp; mac.mnames[mac.lastp] = mac.emb; strcpy(mac.emb, name); mac.emb += lenofname + 1; } else mac.emb--; strcpy(mac.emb, def); mac.emb += lenofdef + 1; return OK; } /* * Get macro definition from table */ uchar *getmac(name) uchar *name; { register int i; register uchar *name1, *mname; for (i = mac.lastp; i > 0; --i) { /*V1.5*/ name1 = name; mname = mac.mnames[i]; while (*(name1++) == *(mname++)) { if (name1[-1] == EOS) return mname; } } return NULL; } /* * Delete macro definition from table */ undefmac(word, infile) uchar *word; FILE *infile; { register int i; register uchar *name, *mname; getcmdwrd(word, infile); skipeol(infile); for (i = mac.lastp; i >= 0; --i) { name = word; mname = mac.mnames[i]; while (*(name++) == *(mname++)) { if (name[-1] == EOS) { mac.lastp = i-1; mac.emb = mac.mnames[i]; return OK; } } } return ERR; } /* * Evaluate macro expansion for re-evaluation */ maceval(macdef, infile) register uchar *macdef; FILE *infile; { uchar *argp[10]; int i; uchar c; uchar line[MAXLINE]; register uchar *linep = line; *linep++ = EOS; getlin(linep, infile); /* * Initialize argp array to substitute empty string * string for any undefined argument */ for (i=0; i<10; ++i) argp[i] = line; for (i=0; i<10; ++i) { linep = skipbl(linep); if (iseol(*linep) || *linep == MORETXT || *linep == EOS) break; if (*linep == '\'' || *linep == '"') { c = *linep++; argp[i] = linep; while (*linep != c && isnteol(*linep) && *linep != EOS) linep++; *linep++ = EOS; } else { argp[i] = linep; linep = skipwd(linep); *linep++ = EOS; } } /* First push back any remaining text or commands */ if (*linep == MORETXT) pbstr(linep+1); /* Now push back macro body */ for (i=strlen(macdef)-1; i>=0; --i) { /* Need we substitute an argument? */ if (i > 0 && macdef[i-1] == '$') { if (!isdigit(macdef[i]) || macdef[i-2] == '$') { /* Re-evaluate escape characters from macro body */ putbak(macdef[i] | NOGUARD); } else { /* but not of any arguments */ pbstr(argp[macdef[i]-'0']); --i; } } else { putbak(macdef[i] | NOGUARD); } } } /* * Push back string into input stream for re-evaluation */ pbstr(p) register uchar p[]; { register int i; for (i=strlen(p)-1; i>=0; --i) { putbak(p[i]); } } /* * Push character back into input stream */ putbak(c) int c; { if (mac.ppb < mac.pbb) { mac.ppb = mac.pbb; *mac.ppb = c; } else { if (mac.ppb >= mac.pbbend-2) { error("*** nro: push back buffer overflow\n"); } *++mac.ppb = c; } /* Avoid evaluating escaped escape characters twice */ if (c == ESCCHAR) *++mac.ppb = ESCCHAR; } /* * Get header or footer title */ gettl(infile, line1, line2, limit1, limit2) FILE *infile; uchar *line1, *line2; int limit1[], limit2[]; { uchar c; while (c = ngetc(infile), isspace(c)); line1[0] = c; if (isnteol(c)) getlin(line1+1, infile); limit1[LEFT] = env.inval; limit1[RIGHT] = env.rmval; if (line2) { strcpy(line2, line1); limit2[LEFT] = limit1[LEFT]; limit2[RIGHT] = limit1[RIGHT]; } } /* * Set parameter and check range */ set(param, val, type, defval, minval, maxval) int *param; int val; uchar type; int defval, minval, maxval; { switch(type) { case '+': *param += val; break; case '-': *param -= val; break; case '*': *param *= val; break; case '/': if (val != 0) *param /= val; else error ("nro: division by zero modification\n"); break; case '%': if (val != 0) *param %= val; else error ("nro: modulo by zero modification\n"); break; default: *param = isdigit(type)? val : defval; break; } if (*param < minval) *param = minval; else if (*param > maxval) *param = maxval; } /* * Skip blanks and tabs in character buffer. * return pointer to first non-space char. */ uchar *skipbl(p) uchar *p; { while (isspace(*p)) ++p; return p; } /* * Skip over word and punctuation */ uchar *skipwd(p) uchar *p; { while (isntspace(*p) && isnteol(*p) && *p != EOS) ++p; return p; } /* * Space vertically n lines */ space(n) int n; { dobrk(); if (pg.lineno > pg.bottom) return; if (pg.lineno == 0) phead(); skip(min(n, pg.bottom+1-pg.lineno)); pg.lineno += n; if (pg.lineno > pg.bottom) pfoot(); }