aboutsummaryrefslogblamecommitdiffstats
path: root/z06.c
blob: 060e40174a17db96bf62909905efb1cb8b8c4f0d (plain) (tree)
1
2
3
4
5
6
7
1181
1182
1183
1184
1185
1186
1187
1188
1189
1190
1191
1192
1193
1194
1195
1196
1197
1198
1199
1200
1201
1202
1203
1204
1205
1206
1207
1208
1209
1210
1211
1212
1213
1214
1215
1216
1217
1218
1219
1220
1221
1222
1223
1224
1225
1226
1227
1228
1229
1230
1231
1232
1233
1234
1235
1236
1237
1238
1239
1240
1241
1242
1243
1244
1245
1246
1247
1248
1249
1250
1251
1252
1253
1254
1255
1256
1257
1258
1259
1260
1261
1262
1263
1264
1265
1266
1267
1268
1269
1270
1271
1272
1273
1274
1275
1276
1277
1278
1279
1280
1281
1282
1283
1284
1285
1286
1287
1288
1289
1290
1291
1292
1293
1294
1295
1296
1297
1298
1299
1300
1301
1302
1303
1304
1305
1306
1307
1308
1309
1310
1311
1312
1313
1314
1315
1316
1317
1318
1319
1320
1321
1322
1323
1324
1325
1326
1327
1328
1329
1330
1331
1332
1333
1334
1335
1336
1337
1338
1339
1340
1341
1342
1343
1344
1345
1346
1347
1348
1349
1350
1351
1352
1353
1354
1355
1356
1357
1358
1359
1360
1361
1362
1363
1364
1365
1366
1367
1368
1369
1370
1371
1372
1373
1374
1375
1376
1377
1378
1379
1380
1381
1382
1383
1384
1385
1386
1387
1388
1389
1390
1391
1392
1393
1394
1395
1396
1397
1398
1399
1400
1401
1402
1403
1404
1405
1406
1407
1408
1409
1410
1411
1412
1413
1414
1415
1416
1417
1418
1419
1420
1421
1422
1423
1424
1425
1426
1427
1428
1429
1430
1431
1432
1433
1434
1435
1436
1437
1438
1439
1440
1441
1442
1443
1444
1445
1446
1447
1448
1449
1450
1451
1452
1453
1454
1455
1456
1457
1458
1459
1460
1461
1462
1463
1464
1465
1466
1467
1468
1469
1470
1471
1472
1473
1474
1475
1476
1477
1478
1479
1480
1481
1482
1483
1484
1485
1486
1487
1488
1489
1490
1491
1492
1493
1494
1495
1496
1497
1498
1499
1500
1501
1502
1503
1504
1505
1506
1507
1508
1509
1510
1511
1512
1513
1514
1515
1516
1517
1518
1519
1520
1521
1522
1523
1524
1525
1526
1527
1528
1529
1530
1531
1532
1533
1534
1535
1536
1537
1538
1539
1540
1541
1542
1543
1544
1545
1546
1547
1548
1549
1550
1551
1552
1553
1554
1555
1556
1557
1558
1559
1560
1561
1562
1563
1564
1565
1566

                                                                               

                                                                               
                                                                               
                                                                               
                                                                               






































                                                                               
                                                                               



















                                                                               

                                                 






                                                               

                                                    







































































                                                                               
                                                                                    

























































































































































































































































                                                                                



                      
                 
                 



































                      
                 
                 



                     

                    












                       

                     
                  
























                                  


                                   


































































































































































































































































































































                                                                                  
                                                       
























































                                                                                  




                                                                        











                                                                        
                            



































































































                                                                                         



                        






































                        
                   
                   



                       

                      










                         

                       
                    

















































































































                                                                                      

                          



                                              
                                                

































































































































































































































































































































































































                                                                                 
/*@z06.c:Parser:PushObj(), PushToken(), etc.@*********************************/
/*                                                                           */
/*  THE LOUT DOCUMENT FORMATTING SYSTEM (VERSION 3.29)                       */
/*  COPYRIGHT (C) 1991, 2003 Jeffrey H. Kingston                             */
/*                                                                           */
/*  Jeffrey H. Kingston (jeff@it.usyd.edu.au)                                */
/*  School of Information Technologies                                       */
/*  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    */
	BOOLEAN		InDefinitions = TRUE;	/* TRUE when in definitions  */
#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 = nilobj, y = nilobj, 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 TEXTURE:
    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 LINK_URL:
    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) == INCG_REPEATED || type(t) == SINCG_REPEATED )
	{ ReadIncGRepeatedDef(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
      InDefinitions = FALSE;
      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 TEXTURE:
      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:
      case LINK_URL:

	/* 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 INCG_REPEATED:
      case SINCG_REPEATED:
      case DATABASE:
      case SYS_DATABASE:
      
	Error(6, 26, "%s symbol out of place",
	  INTERN, &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 */