blob: 39364eb6a86df7264c3e688316a2b718ff9d19a7 [file] [log] [blame]
/*@z06.c:Parser:PushObj(), PushToken(), etc.@*********************************/
/* */
/* 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: z06.c */
/* MODULE: Parser */
/* EXTERNS: InitParser(), Parse() */
/* */
/*****************************************************************************/
#include "externs.h"
#define LEFT_ASSOC 0
#define RIGHT_ASSOC 1
#define PREV_OP 0 /* means an operator was previous */
#define PREV_OBJ 1 /* prev was object not ending in RBR */
#define PREV_RBR 2 /* prev was object ending in RBR */
static OBJECT cross_name; /* name of the cr database */
#define MAX_STACK 100 /* size of parser stacks */
static OBJECT obj_stack[MAX_STACK]; /* stack of objects */
static int otop = -1; /* top of obj_stack */
static OBJECT tok_stack[MAX_STACK]; /* stack of tokens */
static int ttop = -1; /* top of tok_stack */
static int unknown_count = 0; /* no. of unknown symbols */
#if DEBUG_ON
static BOOLEAN debug_now = FALSE; /* TRUE when want to debug */
#endif
/*****************************************************************************/
/* */
/* OBJECT OptimizeCase(x) */
/* */
/* Optimize the @Case expression x, which is known to be of the form */
/* "@BackEnd @Case ...", by evaluating it immediately if its choices */
/* are all literal words or "else". */
/* */
/*****************************************************************************/
static void check_yield(OBJECT y, OBJECT *res_yield, BOOLEAN *all_literals)
{ OBJECT s1, link, z;
Child(s1, Down(y));
debug1(DOP, DD, " checkyield(%s)", EchoObject(y));
if( is_word(type(s1)) )
{ if( StringEqual(string(s1), BackEnd->name) ||
StringEqual(string(s1),STR_ELSE) )
if( *res_yield == nilobj ) *res_yield = y;
}
else if( type(s1) == ACAT )
{ for( link = Down(s1); link != s1; link = NextDown(link) )
{ Child(z, link);
if( type(z) == GAP_OBJ ) continue;
if( is_word(type(z)) )
{ if( StringEqual(string(z), BackEnd->name) ||
StringEqual(string(s1), STR_ELSE))
if( *res_yield == nilobj ) *res_yield = y;
}
else
{ *all_literals = FALSE;
*res_yield = nilobj;
break;
}
}
}
else
{ *all_literals = FALSE;
*res_yield = nilobj;
}
debug2(DOP, DD, " checkyield returning (%s, %s)", EchoObject(*res_yield),
bool(*all_literals));
}
OBJECT OptimizeCase(OBJECT x)
{ OBJECT link, s2, y, res_yield, res; BOOLEAN all_literals;
debug1(DOP, DD, "OptimizeCase(%s)", EchoObject(x));
assert( type(x) == CASE, "OptimizeCase: type(x) != CASE!" );
Child(s2, LastDown(x));
all_literals = TRUE; res_yield = nilobj;
if( type(s2) == YIELD )
{ check_yield(s2, &res_yield, &all_literals);
}
else if( type(s2) == ACAT )
{ for( link = Down(s2); link != s2 && all_literals; link = NextDown(link) )
{
Child(y, link);
debug2(DOP, DD, " OptimizeCase examining %s %s", Image(type(y)),
EchoObject(y));
if( type(y) == GAP_OBJ ) continue;
if( type(y) == YIELD )
{ check_yield(y, &res_yield, &all_literals);
}
else
{ all_literals = FALSE;
res_yield = nilobj;
}
}
}
else
{ all_literals = FALSE;
res_yield = nilobj;
}
if( all_literals && res_yield != nilobj )
{ Child(res, LastDown(res_yield));
DeleteLink(Up(res));
DisposeObject(x);
}
else
{ res = x;
}
debug1(DOP, DD, "OptimizeCase returning %s", EchoObject(res));
return res;
} /* end OptimizeCase */
/*****************************************************************************/
/* */
/* HuntCommandOptions(x) */
/* */
/* See if any of the command-line options apply to closure x. If so, */
/* change x to reflect the overriding command line option. */
/* */
/*****************************************************************************/
static void HuntCommandOptions(OBJECT x)
{ OBJECT colink, coname, coval, opt, y, link, sym; BOOLEAN found;
debug1(DOP, DD, "HuntCommandOptions(%s)", SymName(actual(x)));
sym = actual(x);
for( colink = Down(CommandOptions); colink != CommandOptions;
colink = NextDown(NextDown(colink)) )
{
Child(coname, colink);
Child(coval, NextDown(colink));
debug2(DOP, DD, " hunting \"%s\" with value \"%s\"", string(coname),
EchoObject(coval));
/* set found to TRUE iff coname is the name of an option of x */
found = FALSE;
for( link = Down(sym); link != sym; link = NextDown(link) )
{ Child(opt, link);
if( type(opt) == NPAR && StringEqual(SymName(opt), string(coname)) )
{ found = TRUE;
debug2(DOP, DD, " %s is an option of %s", string(coname),SymName(sym));
break;
}
}
if( found )
{
/* see whether this option is already set within x */
found = FALSE;
for( link = Down(x); link != x; link = NextDown(link) )
{ Child(y, link);
if( type(y) == PAR && actual(y) == opt )
{ found = TRUE;
debug2(DOP, DD, " %s is set in %s", string(coname), SymName(sym));
break;
}
}
if( found )
{
/* option exists already in x: replace it with oval */
DisposeChild(Down(y));
Link(y, coval);
debug2(DOP, DD, " replacing %s value with %s; x =", string(coname),
EchoObject(coval));
ifdebug(DOP, DD, DebugObject(x));
}
else
{
/* option applies to x but has not yet been set in x */
New(y, PAR);
Link(x, y);
actual(y) = opt;
Link(y, coval);
debug2(DOP, DD, " inserting %s with value %s; x =", string(coname),
EchoObject(coval));
ifdebug(DOP, DD, DebugObject(x));
}
}
}
debug1(DOP, DD, "HuntCommandOptions(%s) returning", SymName(sym));
} /* end HuntCommandOptions */
/*****************************************************************************/
/* */
/* PushObj(x) */
/* PushToken(t) */
/* OBJECT PopObj() */
/* OBJECT PopToken() */
/* OBJECT TokenTop */
/* OBJECT ObjTop */
/* */
/* Push and pop from the object and token stacks; examine top item. */
/* */
/*****************************************************************************/
#define PushObj(x) \
{ zz_hold = x; \
if( ++otop < MAX_STACK ) obj_stack[otop] = zz_hold; \
else Error(6, 1, "expression is too deeply nested", \
FATAL, &fpos(obj_stack[otop-1])); \
}
#define PushToken(t) \
{ if( ++ttop < MAX_STACK ) tok_stack[ttop] = t; \
else Error(6, 2, "expression is too deeply nested", \
FATAL, &fpos(tok_stack[ttop-1])); \
}
#define PopObj() obj_stack[otop--]
#define PopToken() tok_stack[ttop--]
#define TokenTop tok_stack[ttop]
#define ObjTop obj_stack[otop]
/*@::DebugStacks(), InsertSpace()@********************************************/
/* */
/* DebugStacks() */
/* */
/* Print debug output of the stacks state */
/* */
/*****************************************************************************/
#if DEBUG_ON
static void DebugStacks(int initial_ttop, int obj_prev)
{ int i;
debug3(ANY, D, " obj_prev: %s; otop: %d; ttop: %d",
obj_prev == PREV_OP ? "PREV_OP" : obj_prev == PREV_OBJ ? "PREV_OBJ" :
obj_prev == PREV_RBR ? "PREV_RBR" : "???", otop, ttop);
for( i = 0; i <= otop; i++ )
debug3(ANY, D, " obj[%d] = (%s) %s", i,
Image(type(obj_stack[i])), EchoObject(obj_stack[i]));
for( i = 0; i <= ttop; i++ )
{ if( i == initial_ttop+1 ) debug0(DOP, DD, " $");
debug3(ANY, D, " tok[%d] = %s (precedence %d)", i,
type(tok_stack[i]) == CLOSURE ?
SymName(actual(tok_stack[i])) : Image(type(tok_stack[i])),
precedence(tok_stack[i]));
}
} /* end DebugStacks */
#endif
/*****************************************************************************/
/* */
/* InsertSpace(t) */
/* */
/* Add any missing catenation operator in front of token t. */
/* */
/*****************************************************************************/
#define InsertSpace(t) \
if( obj_prev ) \
{ int typ, prec; \
if( hspace(t) + vspace(t) > 0 ) \
typ = TSPACE, prec = ACAT_PREC; \
else if( type(t) == LBR || obj_prev == PREV_RBR ) \
typ = TJUXTA, prec = ACAT_PREC; \
else \
typ = TJUXTA, prec = JUXTA_PREC; \
debugcond1(DOP, DD, debug_now, "[ InsertSpace(%s)", Image(typ)); \
while( obj_prev && precedence(TokenTop) >= prec ) \
obj_prev = Reduce(); \
if( obj_prev ) \
{ New(tmp, typ); precedence(tmp) = prec; \
vspace(tmp) = vspace(t); hspace(tmp) = hspace(t); \
mark(gap(tmp)) = FALSE; join(gap(tmp)) = TRUE; \
FposCopy(fpos(tmp), fpos(t)); \
PushToken(tmp); \
} \
debugcond0(DOP, DD, debug_now, "] end InsertSpace()"); \
} /* end InsertSpace */
/*@::Shift(), ShiftObj()@*****************************************************/
/* */
/* static Shift(t, prec, rassoc, leftpar, rightpar) */
/* static ShiftObj(t) */
/* */
/* Shift token or object t onto the stacks; it has the attributes shown. */
/* */
/*****************************************************************************/
#define Shift(t, prec, rassoc, leftpar, rightpar) \
{ debugcond5(DOP, DD, debug_now, "[ Shift(%s, %d, %s, %s, %s)", \
Image(type(t)), prec, rassoc ? "rightassoc" : "leftassoc", \
leftpar ? "lpar" : "nolpar", rightpar ? "rpar" : "norpar"); \
if( leftpar ) \
{ for(;;) \
{ if( !obj_prev ) \
{ PushObj( MakeWord(WORD, STR_EMPTY, &fpos(t)) ); \
obj_prev = PREV_OBJ; \
} \
else if( precedence(TokenTop) >= prec + rassoc ) \
{ obj_prev = Reduce(); \
if( ttop == initial_ttop ) \
{ *token = t; \
debugcond0(DOP, DD, debug_now, \
"] ] end Shift() and Parse(); stacks are:"); \
ifdebugcond(DOP, DD, debug_now, \
DebugStacks(initial_ttop, obj_prev)); \
return PopObj(); \
} \
} \
else break; \
} \
} \
else InsertSpace(t); \
PushToken(t); \
if( rightpar ) obj_prev = FALSE; \
else \
{ obj_prev = Reduce(); \
if( ttop == initial_ttop ) \
{ *token = nilobj; \
debugcond0(DOP, DD, debug_now, \
"] ] end Shift and Parse; stacks are:"); \
ifdebugcond(DOP, DD, debug_now, \
DebugStacks(initial_ttop, obj_prev)); \
return PopObj(); \
} \
} \
debugcond0(DOP, DD, debug_now, "] end Shift()"); \
} /* end Shift */
#define ShiftObj(t, new_obj_prev) \
{ debugcond1(DOP, DD, debug_now, "[ ShiftObj(%s)", Image(type(t))); \
InsertSpace(t); \
PushObj(t); \
obj_prev = new_obj_prev; \
debugcond0(DOP, DD, debug_now, "] end ShiftObj()"); \
}
/*@::Reduce()@****************************************************************/
/* */
/* static Reduce() */
/* */
/* Perform a single reduction of the stacks. */
/* */
/*****************************************************************************/
static BOOLEAN Reduce(void)
{ OBJECT p1, p2, p3, s1, s2, tmp;
OBJECT op; int obj_prev;
debugcond0(DOP, DD, debug_now, "[ Reduce()");
/* ifdebugcond(DOP, DD, debug_now, DebugStacks(0, TRUE)); */
op = PopToken();
obj_prev = PREV_OBJ;
switch( type(op) )
{
case GSTUB_INT:
case GSTUB_EXT:
debug0(DGT, D, "calling TransferEnd( PopObj() ) from Reduce()");
TransferEnd( PopObj() );
New(p1, NULL_CLOS);
PushObj(p1);
Dispose(op);
break;
case GSTUB_NONE:
New(p1, NULL_CLOS);
PushObj(p1);
Dispose(op);
break;
case NULL_CLOS:
case PAGE_LABEL:
case BEGIN_HEADER:
case END_HEADER:
case SET_HEADER:
case CLEAR_HEADER:
case ONE_COL:
case ONE_ROW:
case WIDE:
case HIGH:
case HSHIFT:
case VSHIFT:
case HSCALE:
case VSCALE:
case HCOVER:
case VCOVER:
case SCALE:
case KERN_SHRINK:
case HCONTRACT:
case VCONTRACT:
case HLIMITED:
case VLIMITED:
case HEXPAND:
case VEXPAND:
case START_HVSPAN:
case START_HSPAN:
case START_VSPAN:
case HSPAN:
case VSPAN:
case PADJUST:
case HADJUST:
case VADJUST:
case ROTATE:
case BACKGROUND:
case YIELD:
case BACKEND:
case XCHAR:
case FONT:
case SPACE:
case YUNIT:
case ZUNIT:
case BREAK:
case UNDERLINE:
case COLOUR:
case OUTLINE:
case LANGUAGE:
case CURR_LANG:
case CURR_FAMILY:
case CURR_FACE:
case CURR_YUNIT:
case CURR_ZUNIT:
case COMMON:
case RUMP:
case MELD:
case INSERT:
case ONE_OF:
case NEXT:
case PLUS:
case MINUS:
case TAGGED:
case INCGRAPHIC:
case SINCGRAPHIC:
case PLAIN_GRAPHIC:
case GRAPHIC:
case LINK_SOURCE:
case LINK_DEST:
case OPEN:
case RAW_VERBATIM:
case VERBATIM:
if( has_rpar(actual(op)) )
{ s2 = PopObj();
Link(op, s2);
}
if( has_lpar(actual(op)) )
{ s1 = PopObj();
Link(Down(op), s1);
}
PushObj(op);
break;
case CASE:
if( has_rpar(actual(op)) )
{ s2 = PopObj();
Link(op, s2);
}
if( has_lpar(actual(op)) )
{ s1 = PopObj();
Link(Down(op), s1);
if( type(s1) == BACKEND )
{ op = OptimizeCase(op);
}
}
PushObj(op);
break;
case CROSS:
case FORCE_CROSS:
s2 = PopObj();
Link(op, s2);
s1 = PopObj();
Link(Down(op), s1);
if( type(s1) != CLOSURE )
Error(6, 3, "left parameter of %s is not a symbol (or not visible)",
WARN, &fpos(s1), Image(type(op)));
PushObj(op);
break;
case CLOSURE:
if( has_rpar(actual(op)) )
{ New(s2, PAR);
tmp = PopObj();
Link(s2, tmp);
FposCopy(fpos(s2), fpos(tmp));
actual(s2) = ChildSym(actual(op), RPAR);
Link(op, s2);
}
if( has_lpar(actual(op)) )
{ New(s1, PAR);
tmp = PopObj();
Link(s1, tmp);
FposCopy(fpos(s1), fpos(tmp));
actual(s1) = ChildSym(actual(op), LPAR);
Link(Down(op), s1);
}
PushObj(op);
break;
case LBR:
Error(6, 4, "unmatched %s (inserted %s)", WARN, &fpos(op),
KW_LBR, KW_RBR);
Dispose(op);
obj_prev = PREV_RBR;
break;
case BEGIN:
assert1(FALSE, "Reduce: unmatched", KW_BEGIN);
break;
case RBR:
if( type(TokenTop) == LBR )
{ /* *** FposCopy(fpos(ObjTop), fpos(TokenTop)); *** */
Dispose( PopToken() );
}
else if( type(TokenTop) == BEGIN )
{ if( file_num(fpos(TokenTop)) > 0 )
Error(6, 5, "unmatched %s; inserted %s at%s (after %s)",
WARN, &fpos(op), KW_RBR, KW_LBR,
EchoFilePos(&fpos(TokenTop)), KW_BEGIN);
else
Error(6, 6, "unmatched %s not enclosed in anything",
FATAL, &fpos(op), KW_RBR);
}
else
{ assert1(FALSE, "Reduce: unmatched", KW_RBR);
}
Dispose(op);
obj_prev = PREV_RBR;
break;
case END:
if( type(TokenTop) != BEGIN )
{ assert1(FALSE, "Reduce: unmatched", KW_END);
}
else
{ if( actual(op) != actual(TokenTop) )
{
if( actual(op) == StartSym )
Error(6, 7, "%s %s appended at end of file to match %s at%s",
WARN, &fpos(op), KW_END, SymName(actual(TokenTop)),
KW_BEGIN, EchoFilePos(&fpos(TokenTop)) );
else if( actual(op) == nilobj )
Error(6, 8, "%s replaced by %s %s to match %s at%s",
WARN, &fpos(op), KW_END, KW_END,
actual(TokenTop) == nilobj ? AsciiToFull("??") :
SymName(actual(TokenTop)),
KW_BEGIN, EchoFilePos(&fpos(TokenTop)) );
else
Error(6, 9, "%s %s replaced by %s %s to match %s at%s",
WARN, &fpos(op), KW_END, SymName(actual(op)),
KW_END, SymName(actual(TokenTop)),
KW_BEGIN, EchoFilePos(&fpos(TokenTop)) );
}
Dispose( PopToken() );
}
Dispose(op);
obj_prev = PREV_RBR;
break;
case GAP_OBJ:
p1 = PopObj();
Link(op, p1);
PushObj(op);
obj_prev = PREV_OP;
break;
case VCAT:
case HCAT:
case ACAT:
p3 = PopObj(); p2 = PopObj(); p1 = PopObj();
if( type(p1) == type(op) )
{ Dispose(op);
}
else
{ Link(op, p1);
p1 = op;
}
Link(p1, p2);
Link(p1, p3);
PushObj(p1);
break;
case TSPACE:
case TJUXTA:
p2 = PopObj(); p1 = PopObj();
if( type(p1) != ACAT )
{ New(tmp, ACAT);
Link(tmp, p1);
FposCopy(fpos(tmp), fpos(p1));
p1 = tmp;
}
type(op) = GAP_OBJ;
Link(p1, op);
Link(p1, p2);
PushObj(p1);
break;
default:
assert1(FALSE, "Reduce:", Image(type(op)));
break;
} /* end switch */
debugcond1(DOP, DD, debug_now, "] end Reduce(), returning %s",
obj_prev == PREV_OP ? "PREV_OP" : obj_prev == PREV_OBJ ? "PREV_OBJ" :
obj_prev == PREV_RBR ? "PREV_RBR" : "???");
return obj_prev;
} /* end Reduce */
/*@::SetScope(), InitParser()@************************************************/
/* */
/* SetScope(env, count, vis_only) */
/* */
/* Push scopes required to parse object whose environment is env. */
/* Add to *count the number of scope pushes made. */
/* */
/* If vis_only is true, we only want visible things of the top-level */
/* element of env to be visible in this scope. */
/* */
/*****************************************************************************/
void SetScope(OBJECT env, int *count, BOOLEAN vis_only)
{ OBJECT link, y, yenv; BOOLEAN visible_only;
debugcond2(DOP,DD, debug_now, "[ SetScope(%s, %d)", EchoObject(env), *count);
assert( env != nilobj && type(env) == ENV, "SetScope: type(env) != ENV!" );
if( Down(env) != env )
{ Child(y, Down(env));
assert( LastDown(y) != y, "SetScope: LastDown(y)!" );
link = LastDown(env) != Down(env) ? LastDown(env) : LastDown(y);
Child(yenv, link);
assert( type(yenv) == ENV, "SetScope: type(yenv) != ENV!" );
SetScope(yenv, count, FALSE);
visible_only = vis_only || (use_invocation(actual(y)) != nilobj);
/* i.e. from @Use clause */
PushScope(actual(y), FALSE, visible_only); (*count)++;
/*** this following was a bright idea that did not work owing to
allowing body parameters at times they definitely shouldn't be
BodyParAllowed();
***/
}
debugcond1(DOP, DD, debug_now, "] SetScope returning, count = %d", *count);
} /* end SetScope */
/*****************************************************************************/
/* */
/* InitParser() */
/* */
/* Initialise the parser to contain just GstubExt. */
/* Remember cross_db, the name of the cross reference database, for Parse. */
/* */
/*****************************************************************************/
void InitParser(FULL_CHAR *cross_db)
{ if( StringLength(cross_db) >= MAX_WORD )
Error(6, 10, "cross reference database file name %s is too long",
FATAL, no_fpos, cross_db);
cross_name = MakeWord(WORD, cross_db, no_fpos);
PushToken( NewToken(GSTUB_EXT, no_fpos, 0, 0, DEFAULT_PREC, StartSym) );
} /* end InitParser */
/*@::ParseEnvClosure()@*******************************************************/
/* */
/* static OBJECT ParseEnvClosure(t, encl) */
/* */
/* Parse an object which is a closure with environment. Consume the */
/* concluding @LClos. */
/* */
/*****************************************************************************/
static OBJECT ParseEnvClosure(OBJECT t, OBJECT encl)
{ OBJECT env, res, y; int count, i;
debugcond0(DOP, DDD, debug_now, "ParseEnvClosure(t, encl)");
assert( type(t) == ENV, "ParseEnvClosure: type(t) != ENV!" );
env = t; t = LexGetToken();
while( type(t) != CLOS ) switch( type(t) )
{
case LBR: count = 0;
SetScope(env, &count, FALSE);
y = Parse(&t, encl, FALSE, FALSE);
if( type(y) != CLOSURE )
{
debug1(DIO, D, " Parse() returning %s:", Image(type(y)));
ifdebug(DIO, D, DebugObject(y));
Error(6, 11, "syntax error in cross reference database",
FATAL, &fpos(y));
}
for( i = 1; i <= count; i++ ) PopScope();
AttachEnv(env, y);
debug0(DCR, DDD, " calling SetEnv from ParseEnvClosure (a)");
env = SetEnv(y, nilobj);
t = LexGetToken();
break;
case ENV: y = ParseEnvClosure(t, encl);
debug0(DCR, DDD, " calling SetEnv from ParseEnvClosure (b)");
env = SetEnv(y, env);
t = LexGetToken();
break;
default: Error(6, 12, "error in cross reference database",
FATAL, &fpos(t));
break;
}
Dispose(t);
if( Down(env) == env || Down(env) != LastDown(env) )
Error(6, 13, "error in cross reference database", FATAL, &fpos(env));
Child(res, Down(env));
DeleteNode(env);
debugcond1(DOP, DDD, debug_now, "ParseEnvClosure ret. %s", EchoObject(res));
assert( type(res) == CLOSURE, "ParseEnvClosure: type(res) != CLOSURE!" );
return res;
} /* end ParseEnvClosure */
/*@::Parse()@*****************************************************************/
/* */
/* OBJECT Parse(token, encl, defs_allowed, transfer_allowed) */
/* */
/* Parse input tokens, beginning with *token, looking for an object of the */
/* form { ... } or @Begin ... @End <sym>, and return the object. */
/* The parent definition is encl, and scope has been set appropriately. */
/* Parse reads up to and including the last token of the object */
/* (the right brace or <sym>), and returns nilobj in *token. */
/* */
/* If defs_allowed == TRUE, there may be local definitions in the object. */
/* In this case, encl is guaranteed to be the enclosing definition. */
/* */
/* If transfer_allowed == TRUE, the parser may transfer components to the */
/* galley handler as they are read. */
/* */
/* Note: the lexical analyser returns "@End \Input" at end of input, so the */
/* parser does not have to handle end of input separately. */
/* */
/*****************************************************************************/
OBJECT Parse(OBJECT *token, OBJECT encl,
BOOLEAN defs_allowed, BOOLEAN transfer_allowed)
{ OBJECT t, x, tmp, xsym, env, y, link, res, imps, xlink;
int i, offset, lnum, initial_ttop = ttop;
int obj_prev, scope_count, compulsory_count; BOOLEAN revealed;
debugcond4(DOP, DD, debug_now, "[ Parse(%s, %s, %s, %s)", EchoToken(*token),
SymName(encl), bool(defs_allowed), bool(transfer_allowed));
assert( type(*token) == LBR || type(*token) == BEGIN, "Parse: *token!" );
obj_prev = PREV_OP;
Shift(*token, precedence(*token), 0, FALSE, TRUE);
t = LexGetToken();
if( defs_allowed )
{ ReadDefinitions(&t, encl, LOCAL);
/* if error in definitions, stop now */
if( ErrorSeen() )
Error(6, 14, "exiting now", FATAL, &fpos(t));
if( encl == StartSym )
{
/* read @Use, @Database, and @Prepend commands and defs and construct env */
New(env, ENV);
for(;;)
{
if( type(t) == WORD && (
StringEqual(string(t), KW_DEF) ||
/* StringEqual(string(t), KW_FONTDEF) || */
StringEqual(string(t), KW_LANGDEF) ||
StringEqual(string(t), KW_MACRO) ||
StringEqual(string(t), KW_IMPORT) ||
StringEqual(string(t), KW_EXTEND) ||
StringEqual(string(t), KW_EXPORT) ) )
{
ReadDefinitions(&t, encl, LOCAL);
/* if error in definitions, stop now */
if( ErrorSeen() )
Error(6, 39, "exiting now", FATAL, &fpos(t));
}
else if( type(t) == USE )
{
OBJECT crs, res_env; STYLE style;
Dispose(t); t = LexGetToken();
if( type(t) != LBR )
Error(6, 15, "%s expected after %s", FATAL, &fpos(t),KW_LBR,KW_USE);
debug0(DOP, DD, " Parse() calling Parse for @Use clause");
y = Parse(&t, encl, FALSE, FALSE);
if( is_cross(type(y)) )
{ OBJECT z;
Child(z, Down(y));
if( type(z) == CLOSURE )
{ crs = nilobj;
y = CrossExpand(y, env, &style, &crs, &res_env);
if( crs != nilobj )
{ Error(6, 16, "%s or %s tag not allowed here",
FATAL, &fpos(y), KW_PRECEDING, KW_FOLLOWING);
}
HuntCommandOptions(y);
AttachEnv(res_env, y);
debug0(DCR, DDD, " calling SetEnv from Parse (a)");
env = SetEnv(y, env);
}
else Error(6, 17, "invalid parameter of %s", FATAL, &fpos(y), KW_USE);
}
else if( type(y) == CLOSURE )
{ if( use_invocation(actual(y)) != nilobj )
Error(6, 18, "symbol %s occurs in two %s clauses",
FATAL, &fpos(y), SymName(actual(y)), KW_USE);
use_invocation(actual(y)) = y;
HuntCommandOptions(y);
AttachEnv(env, y);
debug0(DCR, DDD, " calling SetEnv from Parse (b)");
env = SetEnv(y, nilobj);
}
else Error(6, 19, "invalid parameter of %s", FATAL, &fpos(y), KW_USE);
PushScope(actual(y), FALSE, TRUE);
t = LexGetToken();
}
else if( type(t) == PREPEND || type(t) == SYS_PREPEND )
{ ReadPrependDef(type(t), encl);
Dispose(t);
t = LexGetToken();
}
else if( type(t) == DATABASE || type(t) == SYS_DATABASE )
{ ReadDatabaseDef(type(t), encl);
Dispose(t);
t = LexGetToken();
}
else break;
}
/* transition point from defs to content; turn on debugging now */
#if DEBUG_ON
debug_now = TRUE;
#endif
debugcond4(DOP, DD, debug_now, "[ Parse (first) (%s, %s, %s, %s)",
EchoToken(*token), SymName(encl), bool(defs_allowed),
bool(transfer_allowed));
/* load cross-references from previous run, open new cross refs */
if( AllowCrossDb )
{
NewCrossDb = DbCreate(MakeWord(WORD, string(cross_name), no_fpos));
OldCrossDb = DbLoad(cross_name, SOURCE_PATH, FALSE, nilobj,
InMemoryDbIndexes);
}
else OldCrossDb = NewCrossDb = nilobj;
/* tidy up and possibly print symbol table */
FlattenUses();
ifdebug(DST, DD, DebugObject(StartSym));
TransferInit(env);
debug0(DMA, D, "at end of definitions:");
ifdebug(DMA, D, DebugMemory());
}
}
for(;;)
{
debugcond0(DOP, DD, debug_now, "");
ifdebugcond(DOP, DD, debug_now, DebugStacks(0, obj_prev));
debugcond0(DOP, DD, debug_now, "");
debugcond2(DOP, DD, debug_now, ">> %s (precedence %d)", EchoToken(t), precedence(t));
switch( type(t) )
{
case WORD:
if( string(t)[0] == CH_SYMSTART &&
(obj_prev != PREV_OBJ || vspace(t) + hspace(t) > 0) )
{
Error(6, 20, "symbol %s unknown or misspelt",
WARN, &fpos(t), string(t));
if( ++unknown_count > 25 )
{
Error(6, 21, "too many errors (%s lines missing or out of order?)",
FATAL, &fpos(t), KW_SYSINCLUDE);
}
}
ShiftObj(t, PREV_OBJ);
t = LexGetToken();
break;
case QWORD:
ShiftObj(t, PREV_OBJ);
t = LexGetToken();
break;
case VCAT:
case HCAT:
case ACAT:
/* clean up left context */
Shift(t, precedence(t), LEFT_ASSOC, TRUE, TRUE);
/* invoke transfer subroutines if appropriate */
/* *** if( type(t) == VCAT && !has_join(actual(t)) *** */
if( transfer_allowed && type(t) == VCAT && !has_join(actual(t))
&& type(tok_stack[ttop-2]) == GSTUB_EXT )
{
debug0(DGT, DD, " calling TransferComponent from Parse:");
ifdebug(DGT, DD, DebugStacks(0, obj_prev));
TransferComponent( PopObj() );
New(tmp, NULL_CLOS);
FposCopy( fpos(tmp), fpos(t) );
PushObj(tmp);
}
/* push GAP_OBJ token, to cope with 3 parameters */
New(x, GAP_OBJ);
mark(gap(x)) = has_mark(actual(t));
join(gap(x)) = has_join(actual(t));
hspace(x) = hspace(t);
vspace(x) = vspace(t);
precedence(x) = GAP_PREC;
FposCopy( fpos(x), fpos(t) );
Shift(x, GAP_PREC, LEFT_ASSOC, FALSE, TRUE);
/* if op is followed by space, insert {} */
t = LexGetToken();
if( hspace(t) + vspace(t) > 0 )
{ ShiftObj(MakeWord(WORD, STR_EMPTY, &fpos(x)), PREV_OBJ);
}
break;
case CROSS:
case FORCE_CROSS:
case NULL_CLOS:
case PAGE_LABEL:
case BEGIN_HEADER:
case END_HEADER:
case SET_HEADER:
case CLEAR_HEADER:
case ONE_COL:
case ONE_ROW:
case WIDE:
case HIGH:
case HSHIFT:
case VSHIFT:
case HSCALE:
case VSCALE:
case HCOVER:
case VCOVER:
case SCALE:
case KERN_SHRINK:
case HCONTRACT:
case VCONTRACT:
case HLIMITED:
case VLIMITED:
case HEXPAND:
case VEXPAND:
case START_HVSPAN:
case START_HSPAN:
case START_VSPAN:
case HSPAN:
case VSPAN:
case PADJUST:
case HADJUST:
case VADJUST:
case ROTATE:
case BACKGROUND:
case CASE:
case YIELD:
case BACKEND:
case XCHAR:
case FONT:
case SPACE:
case YUNIT:
case ZUNIT:
case BREAK:
case UNDERLINE:
case COLOUR:
case OUTLINE:
case LANGUAGE:
case CURR_LANG:
case CURR_FAMILY:
case CURR_FACE:
case CURR_YUNIT:
case CURR_ZUNIT:
case COMMON:
case RUMP:
case MELD:
case INSERT:
case ONE_OF:
case NEXT:
case TAGGED:
case INCGRAPHIC:
case SINCGRAPHIC:
case PLAIN_GRAPHIC:
case GRAPHIC:
case LINK_SOURCE:
case LINK_DEST:
/* clean up left context of t (these ops are all right associative) */
Shift(t, precedence(t), RIGHT_ASSOC,
has_lpar(actual(t)), has_rpar(actual(t)));
t = LexGetToken();
break;
case VERBATIM:
case RAW_VERBATIM:
/* clean up left context of t */
x = t;
Shift(t, precedence(t), RIGHT_ASSOC,
has_lpar(actual(t)), has_rpar(actual(t)));
/* check for opening brace or begin following, and shift it onto the stacks */
t = LexGetToken();
if( type(t) != BEGIN && type(t) != LBR )
Error(6, 40, "right parameter of %s or %s must be enclosed in braces",
FATAL, &fpos(x), KW_VERBATIM, KW_RAWVERBATIM);
actual(t) = type(x) == VERBATIM ? VerbatimSym : RawVerbatimSym;
Shift(t, LBR_PREC, 0, FALSE, TRUE);
/* read right parameter and add it to the stacks, and reduce */
y = LexScanVerbatim( (FILE *) NULL, type(t) == BEGIN, &fpos(t),
type(x) == RAW_VERBATIM);
ShiftObj(y, PREV_OBJ);
/* carry on, hopefully to the corresponding right brace or @End @Verbatim */
t = LexGetToken();
break;
case PLUS:
case MINUS:
/* clean up left context of t (these ops are all left associative) */
Shift(t, precedence(t), LEFT_ASSOC,
has_lpar(actual(t)), has_rpar(actual(t)));
t = LexGetToken();
break;
case UNEXPECTED_EOF:
Error(6, 22, "unexpected end of input", FATAL, &fpos(t));
break;
case BEGIN:
if( actual(t) == nilobj )
{ Error(6, 23, "%s replaced by %s", WARN, &fpos(t), KW_BEGIN, KW_LBR);
type(t) = LBR;
}
/* NB NO BREAK! */
case LBR:
Shift(t, LBR_PREC, 0, FALSE, TRUE);
t = LexGetToken();
break;
case END:
if( actual(t) == nilobj ) /* haven't sought following symbol yet */
{ x = LexGetToken();
if( type(x) == CLOSURE )
{ actual(t) = actual(x);
Dispose(x);
x = nilobj;
}
else if( type(x) == VERBATIM )
{ actual(t) = VerbatimSym;
Dispose(x);
x = nilobj;
}
else if( type(x) == RAW_VERBATIM )
{ actual(t) = RawVerbatimSym;
Dispose(x);
x = nilobj;
}
else if( type(x) == WORD && string(x)[0] == CH_SYMSTART )
{ Error(6, 24, "unknown or misspelt symbol %s after %s deleted",
WARN, &fpos(x), string(x), KW_END);
actual(t) = nilobj;
Dispose(x);
x = nilobj;
}
else
{ Error(6, 25, "symbol expected after %s", WARN, &fpos(x), KW_END);
actual(t) = nilobj;
}
}
else x = nilobj;
Shift(t, precedence(t), 0, TRUE, FALSE);
t = (x != nilobj) ? x : LexGetToken();
break;
case RBR:
Shift(t, precedence(t), 0, TRUE, FALSE);
t = LexGetToken();
break;
case USE:
case NOT_REVEALED:
case PREPEND:
case SYS_PREPEND:
case DATABASE:
case SYS_DATABASE:
Error(6, 26, "%s symbol out of place",
FATAL, &fpos(t), SymName(actual(t)));
break;
case ENV:
/* only occurs in cross reference databases */
res = ParseEnvClosure(t, encl);
ShiftObj(res, PREV_OBJ);
t = LexGetToken();
break;
case ENVA:
/* only occurs in cross reference databases */
offset = LexNextTokenPos() -StringLength(KW_ENVA)-StringLength(KW_LBR)-1;
Dispose(t); t = LexGetToken();
tmp = Parse(&t, encl, FALSE, FALSE);
env = SetEnv(tmp, nilobj);
ShiftObj(env, PREV_OBJ);
t = LexGetToken();
EnvReadInsert(file_num(fpos(t)), offset, env);
break;
case ENVB:
/* only occurs in cross reference databases */
offset = LexNextTokenPos() -StringLength(KW_ENVB)-StringLength(KW_LBR)-1;
Dispose(t); t = LexGetToken();
env = Parse(&t, encl, FALSE, FALSE);
t = LexGetToken();
res = Parse(&t, encl, FALSE, FALSE);
env = SetEnv(res, env);
ShiftObj(env, PREV_OBJ);
t = LexGetToken();
EnvReadInsert(file_num(fpos(t)), offset, env);
break;
case ENVC:
/* only occurs in cross reference databases */
Dispose(t); t = LexGetToken();
New(res, ENV);
ShiftObj(res, PREV_OBJ);
break;
case ENVD:
/* only occurs in cross reference databases */
Dispose(t); t = LexGetToken();
if( type(t) != QWORD ||
sscanf((char *) string(t), "%d %d", &offset, &lnum) != 2 )
Error(6, 37, "error in cross reference database", FATAL, &fpos(t));
if( !EnvReadRetrieve(file_num(fpos(t)), offset, &env) )
{ LexPush(file_num(fpos(t)), offset, DATABASE_FILE, lnum, TRUE);
Dispose(t); t = LexGetToken();
env = Parse(&t, encl, FALSE, FALSE);
LexPop();
}
else
{ Dispose(t);
}
ShiftObj(env, PREV_OBJ);
t = LexGetToken();
break;
case CENV:
/* only occurs in cross reference databases */
Dispose(t); t = LexGetToken();
env = Parse(&t, encl, FALSE, FALSE);
scope_count = 0;
SetScope(env, &scope_count, FALSE);
t = LexGetToken();
res = Parse(&t, encl, FALSE, FALSE);
for( i = 0; i < scope_count; i++ ) PopScope();
AttachEnv(env, res);
ShiftObj(res, PREV_OBJ);
t = LexGetToken();
break;
case LUSE:
/* only occurs in cross-reference databases */
/* copy invocation from use_invocation(xsym), don't read it */
Dispose(t); t = LexGetToken();
if( type(t) != CLOSURE )
Error(6, 27, "symbol expected following %s", FATAL,&fpos(t),KW_LUSE);
xsym = actual(t);
if( use_invocation(xsym) == nilobj )
Error(6, 28, "%s clause(s) changed from previous run",
FATAL, &fpos(t), KW_USE);
x = CopyObject(use_invocation(xsym), no_fpos);
for( link = LastDown(x); link != x; link = PrevDown(link) )
{ Child(y, link);
if( type(y) == ENV )
{ DeleteLink(link);
break;
}
}
ShiftObj(x, PREV_OBJ);
t = LexGetToken();
break;
case LVIS:
/* only occurs in cross-reference databases */
SuppressVisible();
Dispose(t); t = LexGetToken();
UnSuppressVisible();
if( type(t) != CLOSURE )
Error(6, 29, "symbol expected following %s", FATAL,&fpos(t),KW_LVIS);
/* NB NO BREAK! */
case CLOSURE:
x = t; xsym = actual(x);
/* look ahead one token, which could be an NPAR */
/* or could be @NotRevealed */
PushScope(xsym, TRUE, FALSE);
t = LexGetToken();
if( type(t) == NOT_REVEALED )
{ Dispose(t);
t = LexGetToken();
revealed = FALSE;
}
else revealed = TRUE;
PopScope();
/* if x starts a cross-reference, make it a CLOSURE */
if( is_cross(type(t)) )
{ ShiftObj(x, PREV_OBJ);
break;
}
/* clean up left context of x */
Shift(x,precedence(x),right_assoc(xsym),has_lpar(xsym),has_rpar(xsym));
/* update uses relation if required */
if( encl != StartSym && encl != nilobj )
{ if( has_target(xsym) )
{ uses_galley(encl) = TRUE;
dirty(encl) = (dirty(encl) || dirty(xsym));
}
else if( revealed ) InsertUses(encl, xsym);
}
/* read named parameters */
compulsory_count = 0;
while( (type(t) == CLOSURE && enclosing(actual(t)) == xsym
&& type(actual(t)) == NPAR)
|| (type(t) == LBR && precedence(t) != LBR_PREC) )
{
OBJECT new_par;
/* check syntax and attach the named parameter to x */
if( type(t) == CLOSURE )
{
new_par = t;
t = LexGetToken();
if( type(t) != LBR )
{ Error(6, 30, "%s must follow named parameter %s",
WARN, &fpos(new_par), KW_LBR, SymName(actual(new_par)));
Dispose(new_par);
break;
}
}
else
{
/* compressed form of named parameter */
new_par = NewToken(CLOSURE, &fpos(t), vspace(t), hspace(t),
NO_PREC, ChildSymWithCode(x, precedence(t)));
precedence(t) = LBR_PREC;
}
/* add import list of the named parameter to current scope */
scope_count = 0;
imps = imports(actual(new_par));
if( imps != nilobj )
{ for( link = Down(imps); link != imps; link = NextDown(link) )
{ Child(y, link);
PushScope(actual(y), FALSE, TRUE);
scope_count++;
}
}
/* read the body of the named parameter */
PushScope(actual(new_par), FALSE, FALSE);
tmp = Parse(&t, encl, FALSE, FALSE);
PopScope();
type(new_par) = PAR;
Link(new_par, tmp);
/* pop the scopes pushed for the import list */
for( i = 0; i < scope_count; i++ )
PopScope();
/* check that new_par has not already occurred, then link it to x */
for( link = Down(x); link != x; link = NextDown(link) )
{ Child(y, link);
assert( type(y) == PAR, "Parse: type(y) != PAR!" );
if( actual(new_par) == actual(y) )
{ Error(6, 31, "named parameter %s of %s appears twice", WARN,
&fpos(new_par), SymName(actual(new_par)), SymName(actual(x)));
DisposeObject(new_par);
new_par = nilobj;
break;
}
}
if( new_par != nilobj )
{
/* keep track of the number of compulsory named parameters */
if( is_compulsory(actual(new_par)) )
compulsory_count++;
Link(x, new_par);
}
/* get next token, possibly another NPAR */
PushScope(xsym, TRUE, FALSE); /* allow NPARs only */
if( t == nilobj ) t = LexGetToken();
PopScope();
} /* end while */
/* report absence of compulsory parameters */
debug4(DOP, D, "%s %s %d : %d", EchoFilePos(&fpos(x)),
SymName(xsym), compulsory_count, has_compulsory(xsym));
if( compulsory_count < has_compulsory(xsym) )
{
for( xlink = Down(xsym); xlink != xsym; xlink = NextDown(xlink) )
{ Child(tmp, xlink);
if( type(tmp) == NPAR && is_compulsory(tmp) )
{ for( link = Down(x); link != x; link = NextDown(link) )
{ Child(y, link);
if( type(y) == PAR && actual(y) == tmp )
break;
}
if( link == x )
{
Error(6, 38, "compulsory option %s missing from %s",
WARN, &fpos(x), SymName(tmp), SymName(xsym));
}
}
}
}
/* record symbol name in BEGIN following, if any */
if( type(t) == BEGIN )
{ if( !has_rpar(xsym) )
Error(6, 32, "%s out of place here (%s has no right parameter)",
WARN, &fpos(x), KW_BEGIN, SymName(xsym));
else actual(t) = xsym;
}
/* if x can be transferred, do so */
if( transfer_allowed && has_target(xsym) &&
!has_key(xsym) && filter(xsym) == nilobj )
{
if( !has_rpar(xsym) || uses_count(ChildSym(xsym, RPAR)) <= 1 )
{
debug1(DGT, D, "examining transfer of %s", SymName(xsym));
ifdebug(DGT, D, DebugStacks(initial_ttop, obj_prev));
i = has_rpar(xsym) ? ttop -1 : ttop;
while( is_cat_op(type(tok_stack[i])) ) i--;
if( (type(tok_stack[i])==LBR || type(tok_stack[i])==BEGIN)
&& type(tok_stack[i-1]) == GSTUB_EXT )
{
/* at this point it is likely that x is transferable */
if( has_rpar(xsym) )
{ New(tmp, CLOSURE);
actual(tmp) = InputSym;
FposCopy( fpos(tmp), fpos(t) );
ShiftObj(tmp, PREV_OBJ);
obj_prev = Reduce();
}
x = PopObj();
x = TransferBegin(x);
if( type(x) == CLOSURE ) /* failure: unReduce */
{ if( has_rpar(xsym) )
{ Child(tmp, LastDown(x));
assert(type(tmp)==PAR && type(actual(tmp))==RPAR,
"Parse: cannot undo rpar" );
DisposeChild(LastDown(x));
if( has_lpar(xsym) )
{ Child(tmp, Down(x));
assert(type(tmp)==PAR && type(actual(tmp))==LPAR,
"Parse: cannot undo lpar" );
Child(tmp, Down(tmp));
PushObj(tmp);
DeleteLink(Up(tmp));
DisposeChild(Down(x));
}
PushToken(x); obj_prev = PREV_OP;
}
else
{ PushObj(x);
obj_prev = PREV_OBJ;
}
}
else /* success */
{ obj_prev = PREV_OP;
Shift(x, NO_PREC, 0, FALSE, has_rpar(xsym));
}
}
}
} /* end if has_target */
if( filter(xsym) != nilobj )
{
if( type(t) == BEGIN || type(t) == LBR )
{
/* create filter object and copy parameter into temp file */
tmp = FilterCreate((BOOLEAN) (type(t) == BEGIN), xsym, &fpos(t));
/* push filter object onto stacks and keep going */
Shift(t, precedence(t), 0, FALSE, TRUE);
ShiftObj(tmp, PREV_OBJ);
t = LexGetToken();
}
else Error(6, 33, "right parameter of %s must be enclosed in braces",
FATAL, &fpos(x), SymName(xsym));
}
else if( has_body(xsym) )
{ if( type(t) == BEGIN || type(t) == LBR )
{ PushScope(xsym, FALSE, TRUE);
PushScope(ChildSym(xsym, RPAR), FALSE, FALSE);
PushObj( Parse(&t, encl, FALSE, TRUE) );
obj_prev = Reduce();
PopScope();
PopScope();
if( t == nilobj ) t = LexGetToken();
}
else
{ Error(6, 34, "body parameter of %s must be enclosed in braces",
WARN, &fpos(t), SymName(xsym));
}
}
break;
case OPEN:
x = t; xsym = nilobj;
Shift(t, precedence(t), RIGHT_ASSOC, TRUE, TRUE);
if( type(ObjTop) == CLOSURE ) xsym = actual(ObjTop);
else if( is_cross(type(ObjTop)) && Down(ObjTop) != ObjTop )
{ Child(tmp, Down(ObjTop));
if( type(tmp) == CLOSURE ) xsym = actual(tmp);
}
t = LexGetToken();
if( xsym == nilobj )
Error(6, 35, "invalid left parameter of %s", WARN, &fpos(x), KW_OPEN);
else if( type(t) != BEGIN && type(t) != LBR )
Error(6, 36, "right parameter of %s must be enclosed in braces",
WARN, &fpos(t), KW_OPEN);
else
{ PushScope(xsym, FALSE, TRUE);
tmp = Parse(&t, encl, FALSE, FALSE);
ShiftObj(tmp, PREV_RBR);
PopScope();
if( t == nilobj ) t = LexGetToken();
obj_prev = Reduce();
}
break;
default:
assert1(FALSE, "Parse:", Image(type(t)));
break;
} /* end switch */
} /* end for */
} /* end Parse */