/*@z02.c:Lexical Analyser:Declarations@***************************************/ /* */ /* THE LOUT DOCUMENT FORMATTING SYSTEM (VERSION 3.23) */ /* COPYRIGHT (C) 1991, 2000 Jeffrey H. Kingston */ /* */ /* Jeffrey H. Kingston (jeff@cs.usyd.edu.au) */ /* Basser Department of Computer Science */ /* The University of Sydney 2006 */ /* AUSTRALIA */ /* */ /* This program is free software; you can redistribute it and/or modify */ /* it under the terms of the GNU General Public License as published by */ /* the Free Software Foundation; either Version 2, or (at your option) */ /* any later version. */ /* */ /* This program is distributed in the hope that it will be useful, */ /* but WITHOUT ANY WARRANTY; without even the implied warranty of */ /* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the */ /* GNU General Public License for more details. */ /* */ /* You should have received a copy of the GNU General Public License */ /* along with this program; if not, write to the Free Software */ /* Foundation, Inc., 59 Temple Place, Suite 330, Boston MA 02111-1307 USA */ /* */ /* FILE: z02.c */ /* MODULE: Lexical Analyser */ /* EXTERNS: LexLegalName(), LexInit(), LexPush(), LexPop(), */ /* LexNextTokenPos(), LexGetToken() */ /* */ /* Implementation note: this fast and cryptic lexical analyser is adapted */ /* from Waite, W. M.: The Cost of Lexical Analysis, in Software - Practice */ /* and Experience, v16, pp473-488 (May 1986). */ /* */ /*****************************************************************************/ #include "externs.h" #define BUFFER_SIZE 8192 /* size of buffer for block read */ #define OTHER 0 /* punctuation or other character */ #define LETTER 1 /* letter type */ #define QUOTE 2 /* quoted string delimiter type */ #define ESCAPE 3 /* escape character inside strings */ #define COMMENT 4 /* comment delimiter type */ #define CSPACE 5 /* space character type */ #define FORMFEED 6 /* formfeed character type */ #define TAB 7 /* tab character type */ #define NEWLINE 8 /* newline character type */ #define ENDFILE 9 /* end of file character type */ static unsigned char chtbl[256]; /* type table indexed by a FULL_CHAR */ static FULL_CHAR *chpt; /* pointer to current text character */ static FULL_CHAR *frst; /* address of first buffer character */ static FULL_CHAR *limit; /* just past last char in buffer */ static FULL_CHAR *buf; /* the character buffer start pos */ static int blksize; /* size of block read; others too */ static FULL_CHAR last_char; /* last char read in from file */ static FULL_CHAR *startline; /* position in buff of last newline */ static FILE_NUM this_file; /* number of currently open file */ static FILE *fp; /* current input file */ static FILE_POS file_pos; /* current file position */ static short ftype; /* the type of the current file */ static OBJECT next_token; /* next token if already read */ static int offset; /* where to start reading in file */ static int first_line_num; /* number of first line (if offset) */ static BOOLEAN same_file; /* TRUE if same file as preceding */ static FULL_CHAR *mem_block; /* file buffer */ static int stack_free; /* first free slot in lexical stack */ static struct { FULL_CHAR *chpt; /* pointer to current text character */ FULL_CHAR *frst; /* address of first buffer character */ FULL_CHAR *limit; /* just past last char in buffer */ FULL_CHAR *buf; /* the character buffer start pos */ int blksize; /* size of block read; others too */ FULL_CHAR last_char; /* last char read in from file */ FULL_CHAR *startline; /* position in buff of last newline */ FILE_NUM this_file; /* number of currently open file */ FILE *fp; /* current input file */ FILE_POS file_pos; /* current file position */ short ftype; /* the type of the current file */ OBJECT next_token; /* next token if already read */ int offset; /* where to start reading in file */ int first_line_num; /* number of first line (if offset) */ BOOLEAN same_file; /* TRUE if same file as preceding */ long save_ftell; /* ftell() position if same_file */ FULL_CHAR *mem_block; /* file buffer */ } lex_stack[MAX_LEX_STACK]; /*@::LexLegalName(), LexInit()@***********************************************/ /* */ /* BOOLEAN LexLegalName(str) */ /* */ /* Check whether str is a valid name for a symbol table entry. */ /* Valid names have the BNF form */ /* */ /* ::= { } */ /* ::= { } */ /* ::= { } */ /* */ /* The third form is inaccessible to users and is for internal use only. */ /* */ /*****************************************************************************/ BOOLEAN LexLegalName(FULL_CHAR *str) { int i; BOOLEAN res; debug1(DLA, DDD, "LexLegalName( %s )", str); switch( chtbl[str[0]] ) { case ESCAPE: case LETTER: for( i = 1; chtbl[str[i]] == LETTER; i++ ); res = str[i] == '\0'; break; case OTHER: for( i = 1; chtbl[str[i]] == OTHER; i++ ); res = str[i] == '\0'; break; default: res = FALSE; break; } debug1(DLA, DDD, "LexLegalName returning %s", bool(res)); return res; } /* end LexLegalName */ /*****************************************************************************/ /* */ /* LexInit() */ /* */ /* Initialise character types. Those not touched are 0 (OTHER). */ /* The function initchtbl() assists in initializing the chtbl. */ /* */ /*****************************************************************************/ static void initchtbl(val, str) int val; FULL_CHAR *str; { int i; for( i = 0; str[i] != '\0'; i++ ) chtbl[ str[i] ] = val; } /* end initchtbl */ void LexInit(void) { initchtbl(LETTER, STR_LETTERS_LOWER); initchtbl(LETTER, STR_LETTERS_UPPER); initchtbl(LETTER, STR_LETTERS_SYMSTART); initchtbl(LETTER, STR_LETTERS_UNDERSCORE); initchtbl(LETTER, STR_LETTERS_EXTRA0); initchtbl(LETTER, STR_LETTERS_EXTRA1); initchtbl(LETTER, STR_LETTERS_EXTRA2); initchtbl(LETTER, STR_LETTERS_EXTRA3); initchtbl(LETTER, STR_LETTERS_EXTRA4); initchtbl(LETTER, STR_LETTERS_EXTRA5); initchtbl(LETTER, STR_LETTERS_EXTRA6); initchtbl(LETTER, STR_LETTERS_EXTRA7); initchtbl(QUOTE, STR_QUOTE); initchtbl(ESCAPE, STR_ESCAPE); initchtbl(COMMENT, STR_COMMENT); initchtbl(CSPACE, STR_SPACE); initchtbl(FORMFEED,STR_FORMFEED); initchtbl(TAB, STR_TAB); initchtbl(NEWLINE, STR_NEWLINE); chtbl['\0'] = ENDFILE; stack_free = -1; } /* end LexInit */ /*@::LexPush(), LexPop()@*****************************************************/ /* */ /* LexPush(x, offs, ftype, lnum, same) */ /* */ /* Start reading from the file sequence whose first file is x (subsequent */ /* files are obtained from NextFile). The first file (x) is to be fseeked */ /* to offs. When the sequence is done, ftype determines how to continue: */ /* */ /* ftype action */ /* */ /* SOURCE_FILE last input file ends, return @End \Input */ /* DATABASE_FILE database file, return @End \Input */ /* INCLUDE_FILE include file, must pop lexical analyser and continue */ /* FILTER_FILE filter file, return @End @FilterOut */ /* */ /* lnum is the line number at offs, to be used when creating file pos's */ /* in the tokens returned. same is TRUE when this file is the same as */ /* the file currently being read, in which case there is no need to */ /* close that file and open this one; just an fseek is required. */ /* */ /*****************************************************************************/ void LexPush(FILE_NUM x, int offs, int ftyp, int lnum, BOOLEAN same) { int i; debug5(DLA, DD, "LexPush(%s, %d, %s, %d, %s)", FileName(x), offs, ftyp==SOURCE_FILE ? "source" : ftyp==INCLUDE_FILE ? "include":"database", lnum, bool(same)); if( stack_free >= MAX_LEX_STACK - 1 ) { if( ftyp == INCLUDE_FILE ) Error(2, 1, "too many open files when opening include file %s; open files are:", WARN, PosOfFile(x), FullFileName(x)); else Error(2, 2, "too many open files when opening database file %s; open files are:", WARN, PosOfFile(x), FileName(x)); for( i = stack_free - 1; i >= 0; i-- ) { Error(2, 23, " %s", WARN, no_fpos, EchoFileSource(lex_stack[i].this_file)); } Error(2, 24, "exiting now", FATAL, no_fpos); } if( stack_free >= 0 ) /* save current state */ { lex_stack[stack_free].chpt = chpt; lex_stack[stack_free].frst = frst; lex_stack[stack_free].limit = limit; lex_stack[stack_free].buf = buf; lex_stack[stack_free].blksize = blksize; lex_stack[stack_free].last_char = last_char; lex_stack[stack_free].startline = startline; lex_stack[stack_free].this_file = this_file; lex_stack[stack_free].fp = fp; lex_stack[stack_free].ftype = ftype; lex_stack[stack_free].next_token = next_token; lex_stack[stack_free].offset = offset; lex_stack[stack_free].first_line_num = first_line_num; lex_stack[stack_free].same_file = same_file; lex_stack[stack_free].mem_block = mem_block; FposCopy( lex_stack[stack_free].file_pos, file_pos ); } stack_free += 1; ifdebug(DMA, D, DebugRegisterUsage(MEM_LEX,1, (MAX_LINE+BUFFER_SIZE+2)*sizeof(FULL_CHAR))); mem_block = (FULL_CHAR *) malloc((MAX_LINE+BUFFER_SIZE+2)*sizeof(FULL_CHAR)); if( mem_block == NULL ) Error(2, 3, "run out of memory when opening file %s", FATAL, PosOfFile(x), FullFileName(x)); buf = chpt = &mem_block[MAX_LINE]; last_char = CH_NEWLINE; this_file = x; offset = offs; first_line_num = lnum; same_file = same; ftype = ftyp; next_token = nilobj; *chpt = '\0'; if( same_file ) { lex_stack[stack_free-1].save_ftell = ftell(fp); } else { fp = null; } } /* end LexPush */ /*****************************************************************************/ /* */ /* LexPop() - pop lexical analyser. */ /* */ /*****************************************************************************/ void LexPop(void) { debug0(DLA, DD, "LexPop()"); assert( stack_free > 0, "LexPop: stack_free <= 0!" ); stack_free--; if( same_file ) { fseek(fp, lex_stack[stack_free].save_ftell, SEEK_SET); } else { if( fp != null ) fclose(fp); } ifdebug(DMA, D, DebugRegisterUsage(MEM_LEX,-1,-(MAX_LINE+BUFFER_SIZE+2)* (int) sizeof(FULL_CHAR)) ); free( (char *) mem_block); mem_block = lex_stack[stack_free].mem_block; chpt = lex_stack[stack_free].chpt; frst = lex_stack[stack_free].frst; limit = lex_stack[stack_free].limit; buf = lex_stack[stack_free].buf; blksize = lex_stack[stack_free].blksize; last_char = lex_stack[stack_free].last_char; startline = lex_stack[stack_free].startline; this_file = lex_stack[stack_free].this_file; fp = lex_stack[stack_free].fp; ftype = lex_stack[stack_free].ftype; next_token = lex_stack[stack_free].next_token; offset = lex_stack[stack_free].offset; first_line_num = lex_stack[stack_free].first_line_num; same_file = lex_stack[stack_free].same_file; FposCopy( file_pos, lex_stack[stack_free].file_pos ); } /* end LexPop */ /*@::setword(), LexNextTokenPos(), srcnext()@*********************************/ /* */ /* setword(typ, res, file_pos, str, len) */ /* */ /* Set variable res to a WORD or QWORD token containing string str, etc. */ /* */ /*****************************************************************************/ #define setword(typ, res, file_pos, str, len) \ { NewWord(res, typ, len, &file_pos); \ FposCopy(fpos(res), file_pos); \ for( c = 0; c < len; c++ ) string(res)[c] = str[c]; \ string(res)[c] = '\0'; \ } /*****************************************************************************/ /* */ /* long LexNextTokenPos() */ /* */ /* Equivalent to ftell() on the (buffered) current lex file. */ /* */ /*****************************************************************************/ long LexNextTokenPos(void) { long res; if( next_token != nilobj ) Error(2, 4, "illegal macro invocation in database", FATAL, &fpos(next_token)); res = ftell(fp) - (limit - chpt) - (buf - frst); #if DB_FIX /* uwe: 1997-11-04 * * On NT under Visual C++ ftell() and fseek() always use binary * positions, even if the file was opened in text mode. This means * that every LF in between the CHPT and LIMIT was counted by * ftell() as *TWO* bytes. The pointer arithmetic above adjusts the * ftold value as lout has not yet read chars past CHPT, but it * counts each LF as *ONE* byte, naturally. * * The code below compensates for this binary/text brain death. * * PS: gcc from Cygnus' gnuwin32 has sane ftell() and does *NOT* * need this workaround (I haven't tried compiling lout with gcc * though, as the result will need cygwin.dll to run). */ { register FULL_CHAR *p; for (p = chpt; p < limit; ++p) { if (*p == (FULL_CHAR) CH_NEWLINE) --res; } } #endif /* DB_FIX */ debug1(DLA, DD, "LexNextTokenPos() returning %ld", res); return res; } /*****************************************************************************/ /* */ /* static srcnext() */ /* */ /* Move to new line of input file. May need to recharge buffer. */ /* */ /*****************************************************************************/ static void srcnext(void) { register FULL_CHAR *col; debugcond4(DLA, DD, stack_free <= 1, "srcnext(); buf: %d, chpt: %d, frst: %d, limit: %d", buf - mem_block, chpt - mem_block, frst - mem_block, limit - mem_block); /* if time to transfer last line to area preceding buffer, do so */ if( blksize != 0 && chpt < limit ) { debugcond0(DLA, DD, stack_free <= 1, "srcnext: transferring."); col = buf; while( (*--col = *--limit) != CH_NEWLINE ); frst = col + 1; limit++; blksize = 0; } /* if buffer is empty, read next block */ /*** changed by JK 9/92 from "if( chpt == limit )" to fix long lines bug */ if( chpt >= limit ) { if( chpt > limit ) { col_num(file_pos) = 1; Error(2, 5, "line is too long (or final newline missing)", FATAL, &file_pos); } chpt = frst; blksize = fread( (char *) buf, sizeof(char), BUFFER_SIZE, fp); if( blksize > 0 ) last_char = *(buf + blksize - 1); if( blksize < BUFFER_SIZE && last_char != CH_NEWLINE ) { /* at end of file since blksize = 0; so add missing newline char */ blksize++; last_char = *(buf+blksize-1) = CH_NEWLINE; /* this adjustment breaks LexNextTokenPos, so fatal error if database */ if( ftype == DATABASE_FILE ) { line_num(file_pos) = col_num(file_pos) = 0; Error(2, 25, "a database file must end with a newline; this one doesn't", FATAL, &file_pos); } } debugcond4(DLA, DD, stack_free <= 1, "srcnext: %d = fread(0x%x, %d, %d, fp)", blksize, buf, sizeof(char), BUFFER_SIZE); frst = buf; limit = buf + blksize; *limit = CH_NEWLINE; } /* if nothing more to read, make this clear */ if( chpt >= limit ) { debugcond0(DLA, DD, stack_free <= 1, "srcnext: nothing more to read"); chpt = limit = buf; *limit = '\0'; } debugcond4(DLA, DD, stack_free <= 1, "srcnext returning; buf: %d, chpt: %d, frst: %d, limit: %d", buf - mem_block, chpt - mem_block, frst - mem_block, limit - mem_block); } /* end srcnext */ /*@::LexGetToken()@***********************************************************/ /* */ /* OBJECT LexGetToken() */ /* */ /* Get next token from input. Look it up in symbol table. */ /* */ /*****************************************************************************/ OBJECT LexGetToken(void) { FULL_CHAR *startpos; /* where the latest token started */ register FULL_CHAR *p, *q; /* pointer to current input char */ register int c; /* temporary character (really char) */ OBJECT res; /* result token */ int vcount, hcount; /* no. of newlines and spaces seen */ if( next_token != nilobj ) { next_token = Delete(res = next_token, PARENT); debugcond4(DLA, DD, stack_free <= 1, "LexGetToken%s (in macro) returning %d.%d %s", EchoFilePos(&file_pos), vspace(res), hspace(res), EchoToken(res)); return res; } res = nilobj; p = chpt; vcount = hcount = 0; do switch( chtbl[*p++] ) { case ESCAPE: if( ftype==DATABASE_FILE && *p>='a' && *p<='z' && *(p+1) == '{' /*}*/ ) { res = NewToken(LBR, &file_pos, 0, 0, (unsigned) *p, StartSym); p += 2; } else { col_num(file_pos) = (startpos = p-1) - startline; Error(2, 6, "character %c outside quoted string", WARN, &file_pos, *startpos); } break; case COMMENT: debug1(DLA, DDD, "LexGetToken%s: comment", EchoFilePos(&file_pos)); while( (c = *p++) != CH_NEWLINE && c != '\0' ); if( c == CH_NEWLINE ) { /* do NEWLINE action, only preserve existing horizontal space */ /* and don't count the newline in the vcount. */ chpt = p; srcnext(); line_num(file_pos)++; col_num(file_pos) = 0; startline = (p = chpt) - 1; } else { --p; } break; case CSPACE: case FORMFEED: hcount++; break; case TAB: hcount += 8; break; case NEWLINE: chpt = p; srcnext(); line_num(file_pos)++; col_num(file_pos) = 0; vcount++; hcount = 0; startline = (p = chpt) - 1; break; case ENDFILE: debug0(DLA, DDD, "LexGetToken: endfile"); if( !same_file ) { /* close current file, if any */ if( fp != null ) { fclose(fp); fp = null; this_file = ftype == SOURCE_FILE ? NextFile(this_file) : NO_FILE; } /* open next file */ while( this_file != NO_FILE ) { file_num(file_pos) = this_file; line_num(file_pos) = 1; col_num(file_pos) = 0; fp = OpenFile(this_file, FALSE, TRUE); if( fp != null ) break; Error(2, 7, "cannot open file %s", WARN, &file_pos, FullFileName(this_file)); this_file = ftype == SOURCE_FILE ? NextFile(this_file) : NO_FILE; } } if( fp != null ) { if( offset != 0 ) { debugcond1(DLA, DD, stack_free <= 1, "fseek(fp, %d, SEEK_SET)", offset); fseek(fp, (long) offset, SEEK_SET); offset = 0L; line_num(file_pos) = first_line_num; } frst = limit = chpt = buf; blksize = 0; last_char = CH_NEWLINE; srcnext(); startline = (p = chpt) - 1; hcount = 0; } /* no next file, so take continuation */ else switch( ftype ) { case SOURCE_FILE: case DATABASE_FILE: /* input ends with "@End \Input" then UNEXPECTED_EOF */ res = NewToken(END, &file_pos, 0, 0, END_PREC, StartSym); next_token = NewToken(UNEXPECTED_EOF, &file_pos,0,0,NO_PREC,nilobj); --p; startline = p; break; case FILTER_FILE: /* input ends with "@End @FilterOut" */ res = NewToken(END, &file_pos, 0, 0, END_PREC, FilterOutSym); /* *** next_token = NewToken(CLOSURE,&file_pos,0,0,NO_PREC,FilterOutSym); *** */ --p; startline = p; break; case INCLUDE_FILE: LexPop(); p = chpt; hcount = 0; break; default: assert(FALSE, "unknown file type"); break; } /* end switch */ break; case OTHER: col_num(file_pos) = (startpos = p-1) - startline; while( chtbl[*p++] == OTHER ); c = p - startpos - 1; do { res = SearchSym(startpos, c); --c; --p; } while( c > 0 && res == nilobj ); goto MORE; /* 7 lines down */ case LETTER: col_num(file_pos) = (startpos = p-1) - startline; while( chtbl[*p++] == LETTER ); --p; res = SearchSym(startpos, p - startpos); MORE: if( res == nilobj ) { setword(WORD, res, file_pos, startpos, p-startpos); } else if( type(res) == MACRO ) { if( recursive(res) ) { Error(2, 8, "recursion in macro", WARN, &file_pos); setword(WORD, res, file_pos, startpos, p-startpos); } else { res = CopyTokenList( sym_body(res), &file_pos ); if( res != nilobj ) next_token = Delete(res, PARENT); else hcount = 0; } } else if( predefined(res) == 0 ) { res = NewToken(CLOSURE, &file_pos, 0, 0, precedence(res), res); } else if( predefined(res) == INCLUDE || predefined(res) == SYS_INCLUDE ) { OBJECT t, fname; FILE_NUM fnum; int len; BOOLEAN scope_suppressed; chpt = p; t = LexGetToken(); scope_suppressed = (type(t)==WORD && StringEqual(string(t), KW_LBR)); if( type(t)!=LBR && !scope_suppressed ) { Error(2, 9, "%s expected (after %s)", WARN, &fpos(t), KW_LBR, SymName(res)); Dispose(t); res = nilobj; break; } if( scope_suppressed ) { UnSuppressScope(); Dispose(t); New(t, LBR); } fname = Parse(&t, nilobj, FALSE, FALSE); fname = ReplaceWithTidy(fname, FALSE); if( scope_suppressed ) SuppressScope(); if( !is_word(type(fname)) ) { Error(2, 10, "name of include file expected here", WARN, &fpos(fname)); Dispose(fname); res = nilobj; break; } len = StringLength(string(fname)) - StringLength(SOURCE_SUFFIX); if( len >= 0 && StringEqual(&string(fname)[len], SOURCE_SUFFIX) ) StringCopy(&string(fname)[len], STR_EMPTY); debug0(DFS, D, " calling DefineFile from LexGetToken"); fnum = DefineFile(string(fname), STR_EMPTY, &fpos(fname), INCLUDE_FILE, predefined(res)==INCLUDE ? INCLUDE_PATH : SYSINCLUDE_PATH); Dispose(fname); LexPush(fnum, 0, INCLUDE_FILE, 1, FALSE); res = LexGetToken(); vcount++; /** TEST ADDITION! **/ p = chpt; } else if( predefined(res) == END ) res = NewToken(predefined(res), &file_pos,0,0,precedence(res),nilobj); else res = NewToken(predefined(res), &file_pos,0,0,precedence(res),res); break; case QUOTE: col_num(file_pos) = (startpos = q = p) - 1 - startline; do switch( chtbl[*q++ = *p++] ) { case OTHER: case LETTER: case COMMENT: case CSPACE: case FORMFEED: case TAB: break; case NEWLINE: case ENDFILE: --p; Error(2, 11, "unterminated string", WARN, &file_pos); setword(QWORD, res, file_pos, startpos, q-1-startpos); break; case QUOTE: setword(QWORD, res, file_pos, startpos, q-1-startpos); break; case ESCAPE: q--; if( chtbl[*p] == NEWLINE || chtbl[*p] == ENDFILE ) { Error(2, 12, "unterminated string", WARN, &file_pos); setword(QWORD, res, file_pos, startpos, q-startpos); } else if( octaldigit(*p) ) { int count, ch; count = ch = 0; do { ch = ch * 8 + digitchartonum(*p++); count++; } while( octaldigit(*p) && count < 3 ); if( ch == '\0' ) Error(2, 13, "skipping null character in string", WARN, &file_pos); else *q++ = ch; } else *q++ = *p++; break; default: Error(2, 14, "LexGetToken: error in quoted string", INTERN, &file_pos); break; } while( res == nilobj ); break; default: assert(FALSE, "LexGetToken: bad chtbl[]"); break; } while( res == nilobj ); if( p - startline >= MAX_LINE ) { col_num(file_pos) = 1; Error(2, 15, "line is too long (or final newline missing)",FATAL,&file_pos); } chpt = p; vspace(res) = vcount; hspace(res) = hcount; debugcond5(DLA, DD, stack_free <= 1, "LexGetToken%s returning %s %s %d.%d", EchoFilePos(&file_pos), Image(type(res)), EchoToken(res), vspace(res), hspace(res)); return res; } /* end LexGetToken */ /*@::LexScanVerbatim@*********************************************************/ /* */ /* OBJECT LexScanVerbatim(fp, end_stop, err_pos, lessskip) */ /* */ /* Scan input file and transfer to filter file fp, or if that is NULL, make */ /* a VCAT of objects, one per line (or just a WORD if one line only), and */ /* return that object as the result. If end_stop, terminate at @End, else */ /* terminate at matching right brace. */ /* */ /* If lessskip is true it means that we should skip only up to and */ /* including the first newline character, as opposed to the usual */ /* skipping of all initial white space characters. */ /* */ /*****************************************************************************/ #define print(ch) \ { debug2(DLA, D, "print(%c), bufftop = %d", ch, bufftop); \ if( fp == NULL ) \ { if( bufftop < MAX_BUFF ) \ { if( chtbl[ch] == NEWLINE ) \ { res = BuildLines(res, buff, &bufftop); \ } \ else buff[bufftop++] = ch; \ } \ } \ else putc(ch, fp); \ } #define clear() \ { int i; \ for( i = 0; i < hs_top; i++ ) print(hs_buff[i]); \ hs_top = 0; \ } #define hold(ch) \ { if( hs_top == MAX_BUFF ) clear(); \ hs_buff[hs_top++] = ch; \ } static OBJECT BuildLines(OBJECT current, FULL_CHAR *buff, int *bufftop) { OBJECT wd, res, gp, gpword; int c; /* build a new word and reset the buffer */ setword(WORD, wd, file_pos, buff, *bufftop); debug1(DLA, D, "BuildLines(current, %s)", EchoObject(wd)); *bufftop = 0; if( current == nilobj ) { /* if this is the first word, make it the result */ res = wd; } else { /* if this is the second word, make the result a VCAT */ if( type(current) == WORD ) { New(res, VCAT); FposCopy(fpos(res), fpos(current)); Link(res, current); } else res = current; /* now attach the new word to res, preceded by a one-line gap */ New(gp, GAP_OBJ); mark(gap(gp)) = FALSE; join(gap(gp)) = FALSE; FposCopy(fpos(gp), file_pos); gpword = MakeWord(WORD, AsciiToFull("1vx"), &file_pos); Link(gp, gpword); Link(res, gp); Link(res, wd); } debug1(DLA, D, "BuildLines returning %s", EchoObject(res)); return res; } OBJECT LexScanVerbatim(fp, end_stop, err_pos, lessskip) FILE *fp; BOOLEAN end_stop; FILE_POS *err_pos; BOOLEAN lessskip; { register FULL_CHAR *p; /* pointer to current input char */ int depth; /* depth of nesting of { ... } */ BOOLEAN finished; /* TRUE when finished */ BOOLEAN skipping; /* TRUE when skipping initial spaces */ FULL_CHAR hs_buff[MAX_BUFF]; /* hold spaces here in case last */ int hs_top; /* next free spot in hs_buff */ FULL_CHAR buff[MAX_BUFF]; /* hold line here if not to file */ int bufftop; /* top of buff */ OBJECT res = nilobj; /* result object if not to file */ debug3(DLA, D, "LexScanVerbatim(fp, %s, %s, %s)", bool(end_stop), EchoFilePos(err_pos), bool(lessskip)); if( next_token != nilobj ) { Error(2, 16, "filter parameter in macro", FATAL, err_pos); } p = chpt; depth = 0; finished = FALSE; skipping = TRUE; hs_top = 0; bufftop = 0; while( !finished ) switch( chtbl[*p++] ) { case ESCAPE: case COMMENT: case QUOTE: skipping = FALSE; clear(); print(*(p-1)); break; case CSPACE: case TAB: case FORMFEED: if( !skipping ) hold(*(p-1)); break; case NEWLINE: if( !skipping ) hold(*(p-1)); if( lessskip ) skipping = FALSE; chpt = p; srcnext(); line_num(file_pos)++; col_num(file_pos) = 0; startline = (p = chpt) - 1; break; case ENDFILE: if( fp == NULL ) Error(2, 22, "end of file reached while reading %s", FATAL, err_pos, lessskip ? KW_RAWVERBATIM : KW_VERBATIM); else Error(2, 17, "end of file reached while reading filter parameter", FATAL, err_pos); break; case OTHER: skipping = FALSE; if( *(p-1) == '{' /*}*/ ) { clear(); print(*(p-1)); depth++; } else if( *(p-1) == /*{*/ '}' ) { if( !end_stop && depth == 0 ) { p--; finished = TRUE; } else { clear(); print(*(p-1)); depth--; } } else { clear(); print(*(p-1)); } break; case LETTER: skipping = FALSE; if( *(p-1) == '@' ) { p--; if( end_stop && StringBeginsWith(p, KW_END) ) { finished = TRUE; } else if( StringBeginsWith(p, KW_INCLUDE) || StringBeginsWith(p, KW_SYSINCLUDE) ) { OBJECT incl_fname, t; FILE *incl_fp; int ch; FILE_NUM fnum; BOOLEAN sysinc = StringBeginsWith(p, KW_SYSINCLUDE); clear(); p += sysinc ? StringLength(KW_SYSINCLUDE):StringLength(KW_INCLUDE); chpt = p; t = LexGetToken(); if( type(t) != LBR ) Error(2, 18, "expected %s here (after %s)", FATAL, &fpos(t), KW_LBR, sysinc ? KW_SYSINCLUDE : KW_INCLUDE); incl_fname = Parse(&t, nilobj, FALSE, FALSE); p = chpt; incl_fname = ReplaceWithTidy(incl_fname, FALSE); if( !is_word(type(incl_fname)) ) Error(2, 19, "expected file name here", FATAL,&fpos(incl_fname)); debug0(DFS, D, " calling DefineFile from LexScanVerbatim"); fnum = DefineFile(string(incl_fname), STR_EMPTY, &fpos(incl_fname), INCLUDE_FILE, sysinc ? SYSINCLUDE_PATH : INCLUDE_PATH); Dispose(incl_fname); incl_fp = OpenFile(fnum, FALSE, TRUE); if( incl_fp == NULL ) Error(2, 20, "cannot open include file %s", FATAL, PosOfFile(fnum), FullFileName(fnum)); while( (ch = getc(incl_fp)) != EOF ) print(ch); fclose(incl_fp); } else { clear(); print(*p); p++; } } else { clear(); print(*(p-1)); } break; default: Error(2, 22, "unreadable character (octal %o)",INTERN,&file_pos,*(p-1)); assert(FALSE, "LexScanVerbatim: bad chtbl[]"); break; }; print('\n'); if( p - startline >= MAX_LINE ) { col_num(file_pos) = 1; Error(2, 21, "line is too long (or final newline missing)",FATAL,&file_pos); } chpt = p; if( fp == NULL && res == nilobj ) res = MakeWord(WORD, STR_EMPTY, &file_pos); debug2(DLA, D, "LexScanVerbatim returning %s at %s", EchoObject(res), EchoFilePos(&file_pos)); return res; } /* end LexScanVerbatim */