blob: 452eb1faf06525aae18d32257d40947bc042a4a9 [file] [log] [blame]
/*@z02.c:Lexical Analyser:Declarations@***************************************/
/* */
/* THE LOUT DOCUMENT FORMATTING SYSTEM (VERSION 3.24) */
/* 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 */
/* */
/* <name> ::= <letter> { <letter> } */
/* <name> ::= <special> { <special> } */
/* <name> ::= <escape> { <letter> } */
/* */
/* 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 */