/*
 * $Id: macro1.c,v 1.74 2003/10/23 23:29:17 phil Exp $
 *
 * TODO:
 * have flex() use nextfiodec()?? (what if legal in repeat???)
 * "flex<SP><SP><SP>x" should give right justified result???
 * squawk if nextfiodec called in a repeat w/ a delim?
 *
 * forbid variables/constants in macros
 * forbid text in repeat??
 * forbid start in repeat or macro
 * use same error TLA's as MACRO???
 * IPA error for overbar on LHS of =
 * variables returns value??
 *
 * macro addressing: labels defined during macro are local use only????
 *	spacewar expects this??? (is it wrong?)
 *
 * self-feeding lines: \n legal anywhere \t is
 *	read next token into "token" buffer -- avoid saving "line"?
 *	remove crocks from "define"
 * list title (first line of file) should not be parsed as source?
 * incorrect listing for bare "start"
 * list only 4 digits for address column
 *
 * other;
 * note variables in symbol dump, xref?
 * no "permenant" symbols; flush -p? rename .ini?
 * keep seperate macro/pseudo table?
 * verify bad input(?) detected
 * implement dimension pseudo?
 * remove crocks for '/' and ','?
 */

/*
 * Program:  MACRO1
 * File:     macro1.c
 * Author:   Gary A. Messenbrink <gary@netcom.com> (macro8)
 *	MACRO7 modifications: Bob Supnik <bob.supnik@ljo.dec.com>
 *	MACRO1 modifications: Bob Supnik <bob.supnik@ljo.dec.com>
 *	slashed to be more like real MACRO like by Phil Budne <phil@ultimate.com>
 *
 * Purpose:  A 2 pass PDP-1 assembler
 *
 * NAME
 *    macro1 - a PDP-1 assembler.
 *
 * SYNOPSIS:
 *    macro1 [ -d -p -m -r -s -x ] inputfile inputfile...
 *
 * DESCRIPTION
 *    This is a cross-assembler to for PDP-1 assembly language programs.
 *    It will produce an output file in rim format only.
 *    A listing file is always produced and with an optional symbol table
 *    and/or a symbol cross-reference (concordance).  The permanent symbol
 *    table can be output in a form that may be read back in so a customized
 *    permanent symbol table can be produced.  Any detected errors are output
 *    to a separate file giving the filename in which they were detected
 *    along with the line number, column number and error message as well as
 *    marking the error in the listing file.
 *    The following file name extensions are used:
 *	 .mac	 source code (input)
 *	 .lst	 assembly listing (output)
 *	 .rim	 assembly output in DEC's rim format (output)
 *	 .prm	 permanent symbol table in form suitable for reading after
 *		 the EXPUNGE pseudo-op.
 *	 .sym	 "symbol punch" tape (for DDT, or reloading into macro)
 *
 * OPTIONS
 *    -d   Dump the symbol table at end of assembly
 *    -p   Generate a file with the permanent symbols in it.
 *	   (To get the current symbol table, assemble a file than has only
 *	    START in it.)
 *    -x   Generate a cross-reference (concordance) of user symbols.
 *    -r   Output a tape using only RIM format (else output block loader)
 *    -s   Output a symbol dump tape (loader + loader blocks)
 *    -S file
 *	   Read a symbol tape back in
 *
 * DIAGNOSTICS
 *    Assembler error diagnostics are output to an error file and inserted
 *    in the listing file.  Each line in the error file has the form
 *
 *	 <filename>:<line>:<col> : error:  <message> at Loc = <loc>
 *
 *    An example error message is:
 *
 *	 bintst.7:17:9 : error:  undefined symbol "UNDEF" at Loc = 07616
 *
 *    The error diagnostics put in the listing start with a two character
 *    error code (if appropriate) and a short message.	A carat '^' is
 *    placed under the item in error if appropriate.
 *    An example error message is:
 *
 *	    17 07616 3000	   DAC	   UNDEF
 *	 UD undefined			   ^
 *	    18 07617 1777	   TAD	I  DUMMY
 *
 *    Undefined symbols are marked in the symbol table listing by prepending
 *    a '?' to the symbol.  Redefined symbols are marked in the symbol table
 *    listing by prepending a '#' to the symbol.  Examples are:
 *
 *	 #REDEF	  04567
 *	  SWITCH  07612
 *	 ?UNDEF	  00000
 *
 *    Refer to the code for the diagnostic messages generated.
 *
 * REFERENCES:
 *    This assembler is based on the pal assember by:
 *	 Douglas Jones <jones@cs.uiowa.edu> and
 *	 Rich Coon <coon@convexw.convex.com>
 *
 * COPYRIGHT NOTICE:
 *    This is free software.  There is no fee for using it.  You may make
 *    any changes that you wish and also give it away.	If you can make
 *    a commercial product out of it, fine, but do not put any limits on
 *    the purchaser's right to do the same.  If you improve it or fix any
 *    bugs, it would be nice if you told me and offered me a copy of the
 *    new version.
 *
 *
 * Amendments Record:
 *  Version  Date    by	  Comments
 *  ------- -------  ---  ---------------------------------------------------
 *    v1.0  12Apr96  GAM  Original
 *    v1.1  18Nov96  GAM  Permanent symbol table initialization error.
 *    v1.2  20Nov96  GAM  Added BINPUNch and RIMPUNch pseudo-operators.
 *    v1.3  24Nov96  GAM  Added DUBL pseudo-op (24 bit integer constants).
 *    v1.4  29Nov96  GAM  Fixed bug in checksum generation.
 *    v2.1  08Dec96  GAM  Added concordance processing (cross reference).
 *    v2.2  10Dec96  GAM  Added FLTG psuedo-op (floating point constants).
 *    v2.3   2Feb97  GAM  Fixed paging problem in cross reference output.
 *    v3.0  14Feb97  RMS  MACRO8X features.
 *    	     8Mar97  RMS  MACRO7 released w/ sim8swre
 *    	    13Mar97  RMS  MACRO1 released w/ lispswre
 *    	    28Nov01  RMS  MACRO1 released w/ simtools
 *    	     5Mar03  DP   MACRO1 released w/ ddt1
 *    	    2003     PLB  major reworking, assembles MACRO, DDT
 */


#include <ctype.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>

#define LINELEN              96
#define LIST_LINES_PER_PAGE  60		/* Includes 3 line page header. */
#define NAMELEN             128
#define SYMBOL_COLUMNS        5
#define SYMLEN                7
/*#define SYMSIG	      4		/* EXP: significant chars in a symbol */
#define SYMBOL_TABLE_SIZE  8192
#define MAC_MAX_ARGS         20
#define MAC_MAX_LENGTH     8192
#define MAC_TABLE_LENGTH   1024		/* Must be <= 4096. */

#define MAX_LITERALS	   1000
#define MAX_CONSTANTS	     10		/* max number of "constants" blocks  */

#define XREF_COLUMNS          8

#define ADDRESS_FIELD  0007777
#define INDIRECT_BIT   0010000
#define OP_CODE        0760000

#define CONCISE_LC 072
#define CONCISE_UC 074

/* Macro to get the number of elements in an array. */
#define DIM(a) (sizeof(a)/sizeof(a[0]))

#define ISBLANK(c) ((c==' ') || (c=='\f'))
#define ISEND(c)   ((c=='\0')|| (c=='\n') || (c == '\t'))
#define ISDONE(c)  ((c=='/') || ISEND(c))

#define ISOVERBAR(c) (c == '\\' || c == '~')

/* Macros for testing symbol attributes.  Each macro evaluates to non-zero */
/* (true) if the stated condtion is met. */
/* Use these to test attributes.  The proper bits are extracted and then */
/* tested. */
#define M_DEFINED(s)	(((s) & DEFINED) == DEFINED)
#define M_DUPLICATE(s)	(((s) & DUPLICATE) == DUPLICATE)
#define M_FIXED(s)	(((s) & FIXED) == FIXED)
#define M_LABEL(s)	(((s) & LABEL) == LABEL)
#define M_PSEUDO(s)	(((s) & PSEUDO) == PSEUDO)
#define M_EPSEUDO(s)	(((s) & EPSEUDO) == EPSEUDO)
#define M_MACRO(s)	(((s) & MACRO) == MACRO)
#define M_NOTRDEF(s)	(((s) & NOTRDEF) != 0)

typedef unsigned char BOOL;
typedef unsigned char BYTE;
typedef          int  WORD32;

#ifndef FALSE
  #define FALSE 0
  #define TRUE (!FALSE)
#endif

/* Line listing styles.  Used to control listing of lines. */
enum linestyle_t
{
  LINE, LINE_VAL, LINE_LOC_VAL, LOC_VAL, LINE_LOC
};
typedef enum linestyle_t LINESTYLE_T;

/* Symbol Types. */
/* Note that the names that have FIX as the suffix contain the FIXED bit */
/* included in the value. */
enum symtyp
{
  UNDEFINED = 0000,
  DEFINED   = 0001,
  FIXED     = 0002,
  LABEL     = 0010    | DEFINED,
  REDEFINED = 0020    | DEFINED,
  DUPLICATE = 0040    | DEFINED,
  PSEUDO    = 0100    | FIXED | DEFINED,
  EPSEUDO   = 0200    | FIXED | DEFINED,
  MACRO     = 0400    | DEFINED,
  DEFFIX    = DEFINED | FIXED,
  NOTRDEF   = (MACRO | PSEUDO | LABEL | FIXED) & ~DEFINED
};
typedef enum symtyp SYMTYP;

enum pseudo_t {
    DECIMAL,
    DEFINE,
    FLEX,
    CONSTANTS,
    OCTAL,
    REPEAT,
    START,
    CHAR,
    VARIABLES,
    TEXT,
    NOINPUT,
    EXPUNGE
};
typedef enum pseudo_t PSEUDO_T;

struct sym_t
{
  SYMTYP  type;
  char    name[SYMLEN];
  WORD32  val;
  WORD32  xref_index;
  WORD32  xref_count;
};
typedef struct sym_t SYM_T;

struct emsg_t
{
  char  *list;
  char  *file;
};
typedef struct emsg_t EMSG_T;

struct errsave_t
{
  char   *mesg;
  WORD32  col;
};
typedef struct errsave_t ERRSAVE_T;

/*----------------------------------------------------------------------------*/

/* Function Prototypes */

int     binarySearch( char *name, int start, int symbol_count );
int     compareSymbols( const void *a, const void *b );
SYM_T  *defineLexeme( WORD32 start, WORD32 term, WORD32 val, SYMTYP type );
SYM_T  *defineSymbol( char *name, WORD32 val, SYMTYP type, WORD32 start);
void    errorLexeme( EMSG_T *mesg, WORD32 col );
void    errorMessage( EMSG_T *mesg, WORD32 col );
void    errorSymbol( EMSG_T *mesg, char *name, WORD32 col );
SYM_T   eval( void );
SYM_T  *evalSymbol( void );
void    getArgs( int argc, char *argv[] );
SYM_T   getExpr( void );
WORD32  getExprs( void );
WORD32  incrementClc( void );
WORD32  literal( WORD32 value );
BOOL    isLexSymbol();
char   *lexemeToName( char *name, WORD32 from, WORD32 term );
void    listLine( void );
SYM_T  *lookup( char *name, int type );
void    moveToEndOfLine( void );
void    next(int);
void    onePass( void );
void    printCrossReference( void );
void    printErrorMessages( void );
void    printLine(char *line, WORD32 loc, WORD32 val, LINESTYLE_T linestyle);
void    printPageBreak( void );
void    printPermanentSymbolTable( void );
void    printSymbolTable( void );
BOOL    pseudo( PSEUDO_T val );
void    punchLocObject( WORD32 loc, WORD32 val );
void    punchOutObject( WORD32 loc, WORD32 val );
void    punchLeader( WORD32 count );
void    punchLoader( void );
void    flushLoader( void );
void    readLine( void );
void    saveError( char *mesg, WORD32 cc );
void    topOfForm( char *title, char *sub_title );
void	constants(void);
void	variables(void);
void	eob(void);
void	dump_symbols(void);

/*----------------------------------------------------------------------------*/

/* Table of pseudo-ops (directives) which are used to setup the symbol */
/* table on startup */
SYM_T pseudos[] =
{
  { PSEUDO,  "consta",	CONSTANTS },
  { PSEUDO,  "define",	DEFINE  },	/* Define macro. */
  { PSEUDO,  "repeat",	REPEAT  },
  { PSEUDO,  "start",	START   },	/* Set starting address. */
  { PSEUDO,  "variab",	VARIABLES },
  { PSEUDO,  "text",	TEXT	},
  { PSEUDO,  "noinpu",	NOINPUT	},
  { PSEUDO,  "expung",	EXPUNGE	},
/* the following can appear in expressions: */
  { EPSEUDO, "charac",	CHAR    },
  { EPSEUDO, "decima",	DECIMAL },	/* base 10. */
  { EPSEUDO, "flexo",	FLEX    },
  { EPSEUDO, "octal",	OCTAL   },	/* Read literal constants in base 8. */
};

/* Symbol Table */
/* The table is put in lexical order on startup, so symbols can be */
/* inserted as desired into the initial table. */
#define DIO 0320000
#define JMP 0600000
SYM_T permanent_symbols[] =
{
  /* Memory Reference Instructions */
  { DEFFIX, "and",    0020000 },
  { DEFFIX, "ior",    0040000 },
  { DEFFIX, "xor",    0060000 },
  { DEFFIX, "xct",    0100000 },
  { DEFFIX, "lac",    0200000 },
  { DEFFIX, "lio",    0220000 },
  { DEFFIX, "dac",    0240000 },
  { DEFFIX, "dap",    0260000 },
  { DEFFIX, "dip",    0300000 },
  { DEFFIX, "dio",    0320000 },
  { DEFFIX, "dzm",    0340000 },
  { DEFFIX, "add",    0400000 },
  { DEFFIX, "sub",    0420000 },
  { DEFFIX, "idx",    0440000 },
  { DEFFIX, "isp",    0460000 },
  { DEFFIX, "sad",    0500000 },
  { DEFFIX, "sas",    0520000 },
  { DEFFIX, "mul",    0540000 },
  { DEFFIX, "mus",    0540000 },	/* for spacewar */
  { DEFFIX, "div",    0560000 },
  { DEFFIX, "dis",    0560000 },	/* for spacewar */
  { DEFFIX, "jmp",    0600000 },
  { DEFFIX, "jsp",    0620000 },
  { DEFFIX, "skip",   0640000 },	/* for spacewar */
  { DEFFIX, "cal",    0160000 },
  { DEFFIX, "jda",    0170000 },
  { DEFFIX, "i",      0010000 },
  { DEFFIX, "skp",    0640000 },
  { DEFFIX, "law",    0700000 },
  { DEFFIX, "iot",    0720000 },
  { DEFFIX, "opr",    0760000 },
  { DEFFIX, "nop",    0760000 },
  /* Shift Instructions */
  { DEFFIX, "ral",    0661000 },
  { DEFFIX, "ril",    0662000 },
  { DEFFIX, "rcl",    0663000 },
  { DEFFIX, "sal",    0665000 },
  { DEFFIX, "sil",    0666000 },
  { DEFFIX, "scl",    0667000 },
  { DEFFIX, "rar",    0671000 },
  { DEFFIX, "rir",    0672000 },
  { DEFFIX, "rcr",    0673000 },
  { DEFFIX, "sar",    0675000 },
  { DEFFIX, "sir",    0676000 },
  { DEFFIX, "scr",    0677000 },
  { DEFFIX, "1s",     0000001 },
  { DEFFIX, "2s",     0000003 },
  { DEFFIX, "3s",     0000007 },
  { DEFFIX, "4s",     0000017 },
  { DEFFIX, "5s",     0000037 },
  { DEFFIX, "6s",     0000077 },
  { DEFFIX, "7s",     0000177 },
  { DEFFIX, "8s",     0000377 },
  { DEFFIX, "9s",     0000777 },
  /* Skip Microinstructions */
  { DEFFIX, "sza",    0640100 },
  { DEFFIX, "spa",    0640200 },
  { DEFFIX, "sma",    0640400 },
  { DEFFIX, "szo",    0641000 },
  { DEFFIX, "spi",    0642000 },
  { DEFFIX, "szs",    0640000 },
  { DEFFIX, "szf",    0640000 },
  /*{ DEFFIX, "clo",    0651600 },*/

  /* Operate Microinstructions */
  { DEFFIX, "clf",    0760000 },
  { DEFFIX, "stf",    0760010 },
  { DEFFIX, "cla",    0760200 },
  /*{ DEFFIX, "lap",    0760300 },*/
  { DEFFIX, "hlt",    0760400 },
  { DEFFIX, "xx",     0760400 },
  { DEFFIX, "cma",    0761000 },
  { DEFFIX, "clc",    0761200 },
  { DEFFIX, "lat",    0762200 },
  { DEFFIX, "cli",    0764000 },
  /* IOT's */
  /*{ DEFFIX, "ioh",    0730000 },*/
  { DEFFIX, "rpa",    0730001 },
  { DEFFIX, "rpb",    0730002 },
  { DEFFIX, "rrb",    0720030 },
  { DEFFIX, "ppa",    0730005 },
  { DEFFIX, "ppb",    0730006 },
  { DEFFIX, "tyo",    0730003 },
  { DEFFIX, "tyi",    0720004 },
  { DEFFIX, "dpy",    0730007 },	/* for spacewar, munching squares! */
  { DEFFIX, "lsm",    0720054 },
  { DEFFIX, "esm",    0720055 },
  { DEFFIX, "cbs",    0720056 },
  { DEFFIX, "lem",    0720074 },
  { DEFFIX, "eem",    0724074 },
  { DEFFIX, "cks",    0720033 },
};					/* End-of-Symbols for Permanent Symbol Table */

/* Global variables */
SYM_T *symtab;				/* Symbol Table */
int    symbol_top;			/* Number of entries in symbol table. */

#define LOADERBASE 07751

/* make it relocatable (DDT expects it at 7751) */
#define LOADER_IN LOADERBASE
#define LOADER_B (LOADERBASE+06)
#define LOADER_A (LOADERBASE+07)
#define LOADER_CK (LOADERBASE+025)
#define LOADER_EN1 (LOADERBASE+026)

WORD32 loader[] = {
    0730002,				/* in,	rpb */
    0320000+LOADER_A,			/* 	dio a */
    0100000+LOADER_A,			/* 	xct a */
    0320000+LOADER_CK,			/* 	dio ck */
    0730002,				/* 	rpb */
    0320000+LOADER_EN1,			/* 	dio en1 */
    0730002,				/* b,	rpb */
    0000000,				/* a,	xx */
    0210000+LOADER_A,			/* 	lac i a */
    0400000+LOADER_CK,			/* 	add ck */
    0240000+LOADER_CK,			/* 	dac ck */
    0440000+LOADER_A,			/* 	idx a */
    0520000+LOADER_EN1,			/* 	sas en1 */
    0600000+LOADER_B,			/* 	jmp b */
    0200000+LOADER_CK,			/* 	lac ck */
    0400000+LOADER_EN1,			/* 	add en1 */
    0730002,				/* 	rpb */
    0320000+LOADER_CK,			/* 	dio ck */
    0520000+LOADER_CK,			/* 	sas ck */
    0760400,				/*	hlt */
    0600000+LOADER_IN			/* 	jmp in */
					/* ck,  0 */
					/* en1, 0 */
};

#define LOADERBUFSIZE 0100		/* <=0100, power of 2*/
#define LOADERBUFMASK (LOADERBUFSIZE-1)	/* for block alignment */

WORD32 loaderbuf[LOADERBUFSIZE];
WORD32 loaderbufcount;
WORD32 loaderbufstart;

/*----------------------------------------------------------------------------*/

WORD32 *xreftab;			/* Start of the concordance table. */

ERRSAVE_T error_list[20];
int     save_error_count;

char   s_detected[] = "detected";
char   s_error[]    = "error";
char   s_errors[]   = "errors";
char   s_no[]       = "No";
char   s_page[]     = "Page";
char   s_symtable[] = "Symbol Table";
char   s_xref[]     = "Cross Reference";

/* Assembler diagnostic messages. */
/* Some attempt has been made to keep continuity with the PAL-III and */
/* MACRO-8 diagnostic messages.  If a diagnostic indicator, (e.g., IC) */
/* exists, then the indicator is put in the listing as the first two */
/* characters of the diagnostic message.  The PAL-III indicators where used */
/* when there was a choice between using MACRO-8 and PAL-III indicators. */
/* The character pairs and their meanings are: */
/*      DT  Duplicate Tag (symbol) */
/*      IC  Illegal Character */
/*      ID  Illegal Redefinition of a symbol.  An attempt was made to give */
/*          a symbol a new value not via =. */
/*      IE  Illegal Equals  An equal sign was used in the wrong context, */
/*          (e.g., A+B=C, or TAD A+=B) */
/*      II  Illegal Indirect  An off page reference was made, but a literal */
/*          could not be generated because the indirect bit was already set. */
/*      IR  Illegal Reference (address is not on current page or page zero) */
/*      PE  Current, Non-Zero Page Exceeded (literal table flowed into code) */
/*      RD  ReDefintion of a symbol */
/*      ST  Symbol Table full */
/*      UA  Undefined Address (undefined symbol) */
/*	VR  Value Required */
/*      ZE  Zero Page Exceeded (see above, or out of space) */
EMSG_T  duplicate_label     = { "DT duplicate",  "duplicate label" };
EMSG_T  illegal_blank       = { "IC illegal blank", "illegal blank" };
EMSG_T  illegal_character   = { "IC illegal char",  "illegal character" };
EMSG_T  illegal_expression  = { "IC in expression", "illegal expression" };
EMSG_T  label_syntax        = { "IC label syntax",  "label syntax" };
EMSG_T  not_a_number        = { "IC numeric syntax", "numeric syntax of" };
EMSG_T  number_not_radix    = { "IC radix", "number not in current radix"};
EMSG_T  symbol_syntax       = { "IC symbol syntax", "symbol syntax" };
EMSG_T  illegal_equals      = { "IE illegal =",  "illegal equals" };
EMSG_T  illegal_indirect    = { "II off page",   "illegal indirect" };
EMSG_T  illegal_reference   = { "IR off page",   "illegal reference" };
EMSG_T  undefined_symbol    = { "UD undefined",  "undefined symbol" };
EMSG_T  misplaced_symbol    = { "misplaced symbol", "misplaced symbol" };
EMSG_T  redefined_symbol    = { "RD redefined",  "redefined symbol" };
EMSG_T  value_required      = { "VR value required",  "value required" };
EMSG_T  literal_gen_off     = { "lit generation off",
                                   "literal generation disabled" };
EMSG_T  literal_overflow    = { "PE page exceeded",
                                   "current page literal capacity exceeded" };
EMSG_T  zblock_too_small    = { "expr too small", "ZBLOCK value too small" };
EMSG_T  zblock_too_large    = { "expr too large", "ZBLOCK value too large" };
EMSG_T  no_pseudo_op        = { "not implemented", "Unimplemented pseudo-op" };
EMSG_T  illegal_vfd_value   = { "width out of range",
                                   "VFD field width not in range" };
EMSG_T  no_literal_value    = { "no value",  "No literal value" };
EMSG_T  text_string         = { "no delimiter",
                                    "Text string delimiters not matched" };
EMSG_T  lt_expected         = { "'<' expected",  "'<' expected" };
EMSG_T  symbol_table_full   = { "ST Symbol Tbl full", "Symbol table full" };
EMSG_T  no_macro_name       = { "no macro name", "No name following DEFINE" };
EMSG_T  bad_dummy_arg       = { "bad dummy arg",
                                    "Bad dummy argument following DEFINE" };
EMSG_T  macro_too_long      = { "macro too long", "Macro too long" };
EMSG_T  no_virtual_memory   = { "out of memory",
                                    "Insufficient memory for macro" };
EMSG_T  macro_table_full    = { "Macro Table full", "Macro table full" };
EMSG_T  define_in_repeat    = { "define in a repeat", "Define in a repeat" };

/*----------------------------------------------------------------------------*/

FILE   *errorfile;
FILE   *infile;
FILE   *listfile;
FILE   *listsave;
FILE   *objectfile;
FILE   *objectsave;

char    filename[NAMELEN];
char    listpathname[NAMELEN];
char    sympathname[NAMELEN];
char    objectpathname[NAMELEN];
char   *pathname;
char    permpathname[NAMELEN];

WORD32  mac_count;			/* Total macros defined. */

/*
 * malloced macro bodies, indexed by sym->val dummies are evaluated at
 * invocation time, and value saved in "args"; if recursive macros are
 * desired (without conditionals, how would you escape?), keep a name
 * list here and move symbols to "macinv"
 */
struct macdef {
    int nargs;				/* number of args */
    SYM_T args[MAC_MAX_ARGS+1];		/* symbol for each and one for "r" */
    char body[1];			/* malloc'ed accordingly */
} *mac_defs[MAC_TABLE_LENGTH];

struct macinv {				/* current macro invocation */
    char    mac_line[LINELEN];		/* Saved macro invocation line. */
    WORD32  mac_cc;			/* Saved cc after macro invocation. */
    char   *mac_ptr;			/* Pointer to macro body, NULL if no macro. */
    struct macdef *defn;		/* pointer to definition for dummies */
    struct macinv *prev;		/* previous invocation in stack */
} *curmacro;				/* macro stack */

int	nrepeats;			/* count of nested repeats */

int     list_lineno;
int     list_pageno;
char    list_title[LINELEN];
BOOL    list_title_set;			/* Set if TITLE pseudo-op used. */
char    line[LINELEN];			/* Input line. */
int     lineno;				/* Current line number. */
int     page_lineno;			/* print line number on current page. */
WORD32  listed;				/* Listed flag. */
WORD32  listedsave;

WORD32  cc;				/* Column Counter (char position in line). */
WORD32  clc;				/* Location counter */
BOOL    end_of_input;			/* End of all input files. */
int     errors;				/* Number of errors found so far. */
BOOL    error_in_line;			/* TRUE if error on current line. */
int     errors_pass_1;			/* Number of errors on pass 1. */
int     filix_curr;			/* Index in argv to current input file. */
int     filix_start;			/* Start of input files in argv. */
int	lexstartprev;			/* Where previous lexeme started. */
int	lextermprev;			/* Where previous lexeme ended. */
int	lexstart;			/* Index of current lexeme on line. */
int	lexterm;			/* Index of character after current lexeme. */
int	overbar;			/* next saw an overbar in last token */

int	nconst;				/* number of "constants" blocks */
int	lit_count[MAX_CONSTANTS];	/* # of lits in each block in pass 1 */
WORD32  lit_loc[MAX_CONSTANTS];		/* Base of literal blocks */

int	noinput;			/* don't punch loader */

int	nvars;				/* number of variables */
WORD32	vars_addr;			/* address of "variables" */
WORD32	vars_end;			/* end of "variables" */

/* pass 2 only; */
int	nlit;				/* number of literals in litter[] */
WORD32	litter[MAX_LITERALS];		/* literals */

WORD32  maxcc;				/* Current line length. */
BOOL    nomac_exp;			/* No macro expansion */
WORD32  pass;				/* Number of current pass. */
BOOL    print_permanent_symbols;
WORD32  radix;				/* Default number radix. */
BOOL    rim_mode;			/* RIM mode output. */
BOOL    sym_dump;			/* punch symbol tape */
int     save_argc;			/* Saved argc. */
char   **save_argv;			/* Saved *argv[]. */
WORD32  start_addr;			/* Saved start address. */
BOOL    symtab_print;			/* Print symbol table flag */
BOOL    xref;

SYM_T   sym_undefined = { UNDEFINED, "", 0 };/* Symbol Table Terminator */

/* initial data from SIMH v3.0 pdp1_stddev.c (different encoding of UC/LC) */
#define UC 0100				/* Upper case */
#define LC 0200
#define CHARBITS 077
#define BC LC|UC			/* both case bits */
#define BAD 014				/* unused concise code */

unsigned char ascii_to_fiodec[128] = {
	BAD,	BAD,	BAD,	BAD,	BAD,	BAD,	BAD,	BAD,
	BC|075,	BC|036,	BAD,	BAD,	BAD,	BC|077,	BAD,	BAD,
	BAD,	BAD,	BAD,	BAD,	BAD,	BAD,	BAD,	BAD,
	BAD,	BAD,	BAD,	BAD,	BAD,	BAD,	BAD,	BAD,
	BC|000,	UC|005,	UC|001,	UC|004,	BAD,	BAD,	UC|006,	UC|002,
	LC|057,	LC|055,	UC|073,	UC|054,	LC|033,	LC|054,	LC|073,	LC|021,
	LC|020,	LC|001,	LC|002,	LC|003,	LC|004,	LC|005,	LC|006,	LC|007,
	LC|010,	LC|011,	BAD,	BAD,	UC|007,	UC|033,	UC|010,	UC|021,
	LC|040,	UC|061,	UC|062,	UC|063,	UC|064,	UC|065,	UC|066,	UC|067,
	UC|070,	UC|071,	UC|041,	UC|042,	UC|043,	UC|044,	UC|045,	UC|046,
	UC|047,	UC|050,	UC|051,	UC|022,	UC|023,	UC|024,	UC|025,	UC|026,
	UC|027,	UC|030,	UC|031,	UC|057,	LC|056,	UC|055,	UC|011,	UC|040,
	UC|020,	LC|061,	LC|062,	LC|063,	LC|064,	LC|065,	LC|066,	LC|067,
	LC|070,	LC|071,	LC|041,	LC|042,	LC|043,	LC|044,	LC|045,	LC|046,
	LC|047,	LC|050,	LC|051,	LC|022,	LC|023,	LC|024,	LC|025,	LC|026,
	LC|027,	LC|030,	LC|031,	BAD,	UC|056,	BAD,	UC|003,	BC|075
};

/* for symbol punch tape conversion only!! */
char fiodec_to_ascii[64] = {
	0, '1', '2', '3', '4', '5', '6', '7',
	'8', '9', 0, 0, 0, 0, 0, 0,
	'0', 0, 's', 't', 'u', 'v', 'w', 'x',
	'y', 'z', 0, 0, 0, 0, 0, 0,
	0, 'j', 'k', 'l', 'm', 'n', 'o', 'p',
	'q', 'r', 0, 0, 0, 0, 0, 0,
	0, 'a', 'b', 'c', 'd', 'e', 'f', 'g',
	'h', 'i', 0, 0, 0, 0, 0, 0 };

/* used at startup & for expunge */
void
init_symtab(void) {
    /* Place end marker in symbol table. */
    symtab[0] = sym_undefined;
    symbol_top = 0;
}

/*  Function:  main */
/*  Synopsis:  Starting point.  Controls order of assembly. */
int
main( int argc, char *argv[] )
{
    int     ix;
    int     space;

    save_argc = argc;
    save_argv = argv;

    /* Set the default values for global symbols. */
    print_permanent_symbols = FALSE;
    nomac_exp = TRUE;
    rim_mode = FALSE;			/* default to loader tapes */
    sym_dump = FALSE;
    noinput = FALSE;

    symtab_print = FALSE;
    xref = FALSE;
    pathname = NULL;

    /* init symbol table before processing arguments, so we can
     * load symbol punch tapes on the fly
     */

    /*
     * Setup the error file in case symbol table overflows while
     * installing the permanent symbols.
     */
    errorfile = stderr;
    pass = 0;				/* required for symbol table init */
    symtab = (SYM_T *) malloc( sizeof( SYM_T ) * SYMBOL_TABLE_SIZE );

    if( symtab == NULL ) {
	fprintf( stderr, "Could not allocate memory for symbol table.\n");
	exit( -1 );
    }

    init_symtab();

    /* Enter the pseudo-ops into the symbol table */
    for( ix = 0; ix < DIM( pseudos ); ix++ )
	defineSymbol( pseudos[ix].name, pseudos[ix].val, pseudos[ix].type, 0 );

    /* Enter the predefined symbols into the table. */
    /* Also make them part of the permanent symbol table. */
    for( ix = 0; ix < DIM( permanent_symbols ); ix++ )
	defineSymbol( permanent_symbols[ix].name,
		      permanent_symbols[ix].val,
		      permanent_symbols[ix].type, 0 );

    /* Get the options and pathnames */
    getArgs( argc, argv );

    /* Do pass one of the assembly */
    pass = 1;
    onePass();
    errors_pass_1 = errors;

    /* Set up for pass two */
    objectfile = fopen( objectpathname, "wb" );
    objectsave = objectfile;

    listfile = fopen( listpathname, "w" );
    listsave = listfile;

    /* XXX punch title into tape! */
    punchLeader( 0 );
    if (!rim_mode) {
	punchLoader();
	punchLeader(5);
    }

    if (nlit > 0)
	constants();			/* implied "constants"? */

    /* Do pass two of the assembly */
    errors = 0;
    save_error_count = 0;

    if( xref ) {
	/* Get the amount of space that will be required for the concordance */
	for( space = 0, ix = 0; ix < symbol_top; ix++ ) {
	    symtab[ix].xref_index = space; /* Index into concordance table. */
	    space += symtab[ix].xref_count + 1;
	    symtab[ix].xref_count = 0;	/* Clear the count for pass 2. */
	}
	/* Allocate & clear the necessary space. */
	xreftab = (WORD32 *) calloc( space, sizeof( WORD32 ));
    }
    pass = 2;
    onePass();

    objectfile = objectsave;

    /* Works great for trailer. */
    punchLeader( 1 );

    /* undo effects of NOLIST for any following output to listing file. */
    listfile = listsave;

    /* Display value of error counter. */
    if( errors == 0 ) {
	fprintf( listfile, "\n      %s %s %s\n", s_no, s_errors, s_detected );
    }
    else {
	fprintf( errorfile, "\n      %d %s %s\n", errors, s_detected,
		 ( errors == 1 ? s_error : s_errors ));
	fprintf( listfile, "\n      %d %s %s\n", errors, s_detected,
		 ( errors == 1 ? s_error : s_errors ));
    }

    if( symtab_print )
	printSymbolTable();

    if( print_permanent_symbols )
	printPermanentSymbolTable();

    if( xref )
	printCrossReference();

    fclose( objectfile );
    fclose( listfile );
    if( errors == 0 && errors_pass_1 == 0 ) {
	/* after closing objectfile -- we reuse the FILE *!! */
	if (sym_dump)
	    dump_symbols();
    }
    else
	remove( objectpathname );

    return( errors != 0 );
} /* main() */

/* read a word from a binary punch file */
WORD32
getw(FILE *f)
{
    int i, c;
    WORD32 w;

    w = 0;
    for (i = 0; i < 3;) {
	c = getc(f);
	if (c == -1)
	    return -1;
	if (c & 0200) {			/* ignore if ch8 not punched */
	    w <<= 6;
	    w |= c & 077;
	    i++;
	}
    }
    return w;
}

/*
 * "permute zone bits" like MACRO does for proper sorting
 * (see routine "per" in MACRO) -- it's what DDT expects
 *
 * it's it's own inverse!
 */

WORD32
permute(WORD32 name)
{
    WORD32 temp;

    temp = name & 0202020;		/* get zone bits */
    temp = ((temp << 1) & 0777777) | ((temp >> 17) & 1); /* rotate left */
    name ^= temp;			/* flip zone bits */
    name ^= 0400000;			/* toggle sign */
    return name;
}

/* add a symbol from a "symbol punch" tape */
void
addsym(WORD32 sym, WORD32 val)
{
    char name[4];

    sym = permute(sym);
    name[0] = fiodec_to_ascii[(sym >>12) & 077];
    name[1] = fiodec_to_ascii[(sym >> 6) & 077];
    name[2] = fiodec_to_ascii[sym & 077];
    name[3] = '\0';
    defineSymbol( name, val, LABEL, 0);
}

void
read_symbols(char *fname)
{
    FILE *f;

    f = fopen(fname, "rb");
    if (!f) {
	perror(fname);
	exit(1);
    }

    /* skip loader */
    for (;;) {
	WORD32 w;

	w = getw(f);
	if (w == -1)
	    goto err;			/* XXX complain? */
	if ((w & OP_CODE) == JMP)
	    break;
	if ((w & OP_CODE) != DIO)
	    goto err;			/* XXX complain? */
	w = getw(f);
	if (w == -1)
	    goto err;			/* XXX complain? */
    }


    /* XXX should push block reader down into a co-routine */
    for (;;) {
	WORD32 start, end, sum;

	start = getw(f);
	if ((start & OP_CODE) == JMP) {
	    fclose(f);
	    return;
	}

	if (start == -1 || (start & OP_CODE) != DIO)
	    goto err;

	end = getw(f);
	if (end == -1 || (end & OP_CODE) != DIO)
	    goto err;			/* XXX complain? */

	sum = start + end;
	while (start < end) {
	    WORD32 sym, val;
	    sym = getw(f);
	    if (sym == -1)
		goto err;
	    sum += sym;
	    start++;
	    /* XXX handle block boundaries? */
	    if (start >= end)
		goto err;
	    val = getw(f);
	    if (val == -1)
		goto err;
	    /*printf("%06o %06o\n", sym, val);*/
	    addsym(sym, val);
	    sum += val;
	    start++;
	}
	start = getw(f);		/* eat checksum XXX verify? */
	if (start == -1)
	    goto err;
	/* roll over all the overflows at once */
	if (sum & ~0777777) {
	    sum = (sum & 0777777) + (sum >> 18);
	    if (sum & 01000000)			/* one more time */
		sum++;
	}
	if (start != sum)
	    goto err;
    }
err:
    fprintf(stderr, "error reading symbol file %s\n", fname);
    exit(1);
}

/*  Function:  getArgs */
/*  Synopsis:  Parse command line, set flags accordingly and setup input and */
/*             output files. */
void getArgs( int argc, char *argv[] )
{
  WORD32  len;
  WORD32  ix, jx;

  /* Set the defaults */
  infile = NULL;
  listfile = NULL;
  listsave = NULL;
  objectfile = NULL;
  objectsave = NULL;

  for( ix = 1; ix < argc; )
  {
    if( argv[ix][0] == '-' )
    {
      char *switches = argv[ix++];
      for( jx = 1; switches[jx] != 0; jx++ )
      {
        switch( switches[jx] )
        {
        case 'd':
          symtab_print = TRUE;
          break;

        case 'r':
          rim_mode = TRUE;		/* punch pure rim-mode tapes */
          break;

	case 's':
	  sym_dump = TRUE;
	  break;

        case 'm':
          nomac_exp = FALSE;
          break;

        case 'p':
          print_permanent_symbols = TRUE;
          break;

        case 'x':
          xref = TRUE;
          break;

	case 'S':
	  if (ix <= argc)
	      read_symbols(argv[ix++]);
	  break;

        default:
          fprintf( stderr, "%s: unknown flag: %s\n", argv[0], argv[ix] );
          fprintf( stderr, " -d -- dump symbol table\n" );
          fprintf( stderr, " -m -- output macro expansions\n" );
          fprintf( stderr, " -p -- output permanent symbols to file\n" );
          fprintf( stderr, " -r -- output RIM format file\n" );
          fprintf( stderr, " -s -- output symbol punch tape to file\n" ); 
          fprintf( stderr, " -S file -- read symbol punch tape\n" );
	  fprintf( stderr, " -x -- output cross reference to file\n" );
          fflush( stderr );
          exit( -1 );
        } /* end switch */
      } /* end for */
    }
    else
    {
      filix_start = ix;
      pathname = argv[ix];
      break;
    }
  } /* end for */

  if( pathname == NULL )
  {
    fprintf( stderr, "%s:  no input file specified\n", argv[0] );
    exit( -1 );
  }

  len = strlen( pathname );
  if( len > NAMELEN - 5 )
  {
    fprintf( stderr, "%s: pathname \"%s\" too long\n", argv[0], pathname );
    exit( -1 );
  }

  /* Now make the pathnames */
  /* Find last '.', if it exists. */
  jx = len - 1;
  while( pathname[jx] != '.'  && pathname[jx] != '/'
      && pathname[jx] != '\\' && jx >= 0 )
  {
    jx--;
  }

  switch( pathname[jx] )
  {
  case '.':
    break;

  case '/':
  case '\\':
    jx = len;
    break;

  default:
    break;
  }

  /* Add the pathname extensions. */
  strncpy( objectpathname, pathname, jx );
  objectpathname[jx] = '\0';
  strcat( objectpathname, ".rim");

  strncpy( listpathname, pathname, jx );
  listpathname[jx] = '\0';
  strcat( listpathname, ".lst" );

  strncpy( permpathname, pathname, jx );
  permpathname[jx] = '\0';
  strcat( permpathname, ".prm" );

  strncpy( sympathname, pathname, jx );
  sympathname[jx] = '\0';
  strcat( sympathname, ".sym" );

  /* Extract the filename from the path. */
  if( isalpha( pathname[0] ) && pathname[1] == ':' && pathname[2] != '\\' )
      pathname[1] = '\\';		/* MS-DOS style pathname */

  jx = len - 1;
  while( pathname[jx] != '/' && pathname[jx] != '\\' && jx >= 0 )
      jx--;
  strcpy( filename, &pathname[jx + 1] );
} /* getArgs() */


int
invokeMacro(int index)
{
    struct macinv *mip;
    struct macdef *mdp;
    int jx;

    mdp = mac_defs[index];
    if (mdp == NULL || mdp->body[0] == '\0')
	return 0;

    /* Find arguments. */
    while (ISBLANK(line[lexstart]))
	next(0);

    mip = calloc(1, sizeof(struct macinv));
    if (!mip) {
	fprintf(stderr, "could not allocate memory for macro invocation\n");
	exit(1);
    }
    mip->defn = mdp;

    /* evaluate args, saving values in SYM_T entries in defn.
     * (cannot have recursive macros)
     */
    mdp->args[0].val = clc;		/* r is location at start */
    for( jx = 1; !ISDONE(line[lexstart]) && jx <= MAC_MAX_ARGS; ) {
	WORD32 val;

	next(0);
	if (ISDONE(line[lexstart]))
	    break;

	if (line[lexstart] == ',')
	    next(0);

	while( ISBLANK( line[lexstart] ))
	    next(0);

	if (ISDONE(line[lexstart]))
	    break;

	val = getExprs();

	/* ignore excess values silently? */
	if (jx <= mdp->nargs)
	    mdp->args[jx].val = val;
	jx++;
    } /* end for */

    /* XXX complain if too few actuals? -- nah */
    while (jx <= mdp->nargs)
	mdp->args[jx++].val = 0;

    strcpy(mip->mac_line, line);	/* save line */
    mip->mac_cc = cc;			/* save position in line */
    mip->mac_ptr = mdp->body;
    mip->prev = curmacro;		/* push the old entry */
    curmacro = mip;			/* step up to the plate! */
    return 1;
}

/* process input; used by onePass and repeat */
void
processLine() {
    if (!list_title_set) {
	char *cp;

	/* assert(sizeof(title) >= sizeof(line)); */
	strcpy(list_title, line);

	if ((cp = strchr(list_title, '\n')))
	    *cp = '\0';

	if (list_title[0]) {
	    list_title_set = TRUE;
	    fprintf(stderr, "%s - pass %d\n", list_title, pass );
	    /* XXX punch title into tape  banner (until an '@' seen) */
	}
	return;
    }

    for (;;) {
	int jx;
	SYM_T evalue;

	next(0);
	if( end_of_input )
	    return;

	if( ISEND( line[lexstart] )) {
	    if (line[lexstart] != '\t')
		return;
	    continue;
	}
	if (line[lexstart] == '/')	/* comment? */
	    return;			/* done */

	/* look ahead for 'exp/' */
	/* skip until whitespace or terminator */
	for( jx = lexstart; jx < maxcc; jx++ )
	    if( ISBLANK(line[jx]) || ISDONE(line[jx]))
		break;
	if( line[jx] == '/') {		/* EXP/ set location */
	    WORD32  newclc;

	    newclc = getExprs();

	    /* Do not change Current Location Counter if an error occurred. */
	    if( !error_in_line )
		clc = newclc;

	    printLine( line, newclc, 0, LINE_LOC );
	    cc = jx + 1;
	    next(0);			/* discard slash */
	    continue;
	}

	switch( line[lexterm] ) {
	case ',':
	    if( isLexSymbol()) {
		WORD32 val;
		SYM_T *sym;
		char name[SYMLEN];

		/* Use lookup so symbol will not be counted as reference. */
		sym = lookup(lexemeToName(name, lexstart, lexterm), UNDEFINED);

		if (curmacro) {
		    /* relative during macro expansion!! */
		    val = clc - curmacro->defn->args[0].val;
		}
		else
		    val = clc;

		if( M_DEFINED( sym->type )) {
		    if( sym->val != val && pass == 2 )
			errorSymbol( &duplicate_label, sym->name, lexstart );
		    sym->type |= DUPLICATE;	/* XXX never used! */
		}
		/* Must call define on pass 2 to generate concordance. */
		defineLexeme( lexstart, lexterm, val, LABEL );
	    }
	    else if (isdigit(line[lexstart])) {	/* constant, */
		int i;
		WORD32 val = 0;

		for( i = lexstart; i < lexterm; i++ ) {
		    if( isdigit( line[i] )) {
			int digit;
			digit = line[i] - '0';
			if( digit >= radix ) {
			    errorLexeme( &number_not_radix, i );
			    val = 0;
			    break;
			}
			val = val * radix + digit;
		    }
		    else {
			errorLexeme( &not_a_number, lexstart );
			val = 0;
			break;
		    }
		}
		if (i == lexterm) {
		    if( clc != val && pass == 2 )
			errorLexeme( &duplicate_label, lexstart); /* XXX */
		}
	    }
	    else
		errorLexeme( &label_syntax, lexstart );
	    next(0);			/* skip comma */
	    continue;

	case '=':
	    if( isLexSymbol()) {
		WORD32 start, term, val;

		start = lexstart;
		term = lexterm;
		next(0);		/* skip symbol */
		next(0);		/* skip trailing = */
		val = getExprs();
		defineLexeme( start, term, val, DEFINED );
		printLine( line, 0, val, LINE_VAL );
	    }
	    else {
		errorLexeme( &symbol_syntax, lexstartprev );
		next(0);		/* skip symbol */
		next(0);		/* skip trailing = */
		getExprs();		/* skip expression */
	    }
	    continue;
	} /* switch on terminator */

	if( isLexSymbol()) {
	    SYM_T  *sym;
	    WORD32  val;

	    sym = evalSymbol();
	    val = sym->val;
	    if( M_MACRO(sym->type)) {
		if (!invokeMacro(val))
		    next(0);		/* bad defn? or body is empty! */
		continue;
	    } /* macro invocation */
	    else if( M_PSEUDO(sym->type)) {	/* NO EPSEUDOs */
		pseudo( (PSEUDO_T)val & 0777777 );
		continue;
	    } /* pseudo */
	} /* macro, or non-char pseudo */

	evalue = getExpr();
	if (evalue.type != PSEUDO) {	/* not a  bare pseudo-op? */
	    if (line[lexstart] == ',') {	/* EXP, */
		if(evalue.val != clc && pass == 2 )
		    errorLexeme( &duplicate_label, lexstart); /* XXX */
	    }
	    else if (line[lexstart] == '/') {	/* EXP/ */
		clc = evalue.val;
		printLine( line, clc, 0, LINE_LOC );
		next(0);
	    }
	    else {
		punchOutObject( clc, evalue.val & 0777777); /* punch it! */
		incrementClc();
	    }
	}
    } /* forever */
}

/*  Function:  onePass */
/*  Synopsis:  Do one assembly pass. */
void onePass() {
    int     ix;

    clc = 4;				/* Default location is 4 */
    start_addr = 0;			/* No starting address. */
    nconst = 0;				/* No constant blocks seen */
    nvars = 0;				/* No variables seen */

    while (curmacro) {			/* pop macro stack */
	struct macinv *mp;

	mp = curmacro->prev;
	free(curmacro);
	curmacro = mp;
    }

    for( ix = 0; ix < mac_count; ix++) {
	if (mac_defs[ix])
	    free( mac_defs[ix] );
	mac_defs[ix] = NULL;
    }
    mac_count = 0;			/* No macros defined. */

    listed = TRUE;
    lineno = 0;
    list_pageno = 0;
    list_lineno = 0;
    list_title_set = FALSE;
    page_lineno = LIST_LINES_PER_PAGE;	/* Force top of page for new titles. */
    radix = 8;				/* Initial radix is octal (base 8). */

    /* Now open the first input file. */
    end_of_input = FALSE;
    filix_curr = filix_start;		/* Initialize pointer to input files. */
    if(( infile = fopen( save_argv[filix_curr], "r" )) == NULL ) {
	fprintf( stderr, "%s: cannot open \"%s\"\n", save_argv[0],
		save_argv[filix_curr] );
	exit( -1 );
    }

    for (;;) {
	readLine();
	if (end_of_input) {
	    eob();
	    fclose( infile );
	    return;
	}
	processLine();
    } /* forever */
} /* onePass */


/*  Function:  getExprs */
/*  Synopsys:  gutted like a fish */
WORD32 getExprs()
{
    SYM_T sym;

    sym = getExpr();
    if (sym.type == PSEUDO)
	errorMessage( &value_required, lexstart ); /* XXX wrong pointer? */

    return sym.val & 0777777;
} /* getExprs */


SYM_T getExpr()
{
    SYM_T sym;

    sym = eval();

    /* Here we assume the current lexeme is the operator separating the */
    /* previous operator from the next, if any. */

    for (;;) {
	int space;
	/*
	 * falling out of switch breaks loop and returns from routine
	 * so if you want to keep going, you must "continue"!!
	 */
	space = FALSE;
	switch( line[lexstart] ) {
	case ' ':
	    space = TRUE;
	    /* fall */
	case '+':			/* add */
	    next(1);			/* skip operator */
	    if (space && ISEND(line[lexstart]))	/* tollerate a trailing space */
		return sym;
	    sym.val += eval().val;	/* XXX look at type? */
	    sym.type = DEFINED;
	    if( sym.val >= 01000000 )
		sym.val = ( sym.val + 1 ) & 0777777;
	    continue;

	case '-':			/* subtract */
	    next(1);			/* skip over the operator */
	    sym.val += eval().val ^ 0777777; /* XXX look at type? */
	    sym.type = DEFINED;
	    if( sym.val >= 01000000 )
		sym.val = ( sym.val + 1 ) & 0777777;
	    continue;

	case '*':			/* multiply */
	    next(1);			/* skip over the operator */
	    sym.val *= eval().val;
	    sym.type = DEFINED;
	    if( sym.val >= 01000000 )
		sym.val = ( sym.val + 1 ) & 0777777;
	    continue;

#if 0
	case '%':			/* divide !??? */
	    /*
	     * neither '%' nor the divide symbol appear in FIO-DEC,
	     * does any known program use such an operator?
	     * Easily confused for "MOD", which is how C uses '%'!
	     */
	    next(1);
	    sym.val /= eval().val;
	    sym.type = DEFINED;
	    continue;
#endif

	case '&':			/* and */
	    next(1);			/* skip over the operator */
	    sym.val &= eval().val;
	    sym.type = DEFINED;
	    continue;

	case '!':			/* or */
	    next(1);			/* skip over the operator */
	    sym.val |= eval().val;
	    sym.type = DEFINED;
	    continue;

	case '/':
	case ')':
	case ']':
	case ':':
	case ',':
	    break;

	case '=':
	    errorMessage( &illegal_equals, lexstart );
	    moveToEndOfLine();
	    sym.val = 0;
	    break;

	default:
	    if (!ISEND(line[lexstart])) {
		errorMessage( &illegal_expression, lexstart );
		moveToEndOfLine();
		sym.val = 0;
		break;
	    }
	} /* switch */
	break;				/* break loop!! */
    } /* "forever" */
    return( sym );
} /* getExpr */

/*
 * return fio-dec code for next char
 * embeds shifts as needed
 */
int
nextfiodec(int *ccase, int delim)
{
    unsigned char c;

    for (;;) {
	if (cc >= maxcc) {
	    if (delim == -1)
		return -1;

	    /* XXX MUST NOT BE IN A REPEAT!! */
	    readLine();			/* danger will robinson! */
	    if (end_of_input)
		return -1;
	}
	c = line[cc];
	switch (c) {
	case '\n':
	    c = '\r';
	    break;
	case '\r':
	    continue;
	}
	break;
    }

    if (delim != -1 && c == delim) {
	if (*ccase == LC) {
	    cc++;			/* eat delim */
	    return -1;
	}
	*ccase = LC;
	return CONCISE_LC;		/* shift down first */
    }

    if (c > 0177) {			/* non-ascii */
	errorMessage( &illegal_character, cc );
	c = 0;				/* space?! */
    }

    c = ascii_to_fiodec[c&0177];
    if (c == BAD) {
	errorMessage( &illegal_character, cc );
	c = 0;				/* space?! */
    }

    if (!(c & *ccase)) {		/* char not in current case? */
	*ccase ^= BC;			/* switch case */
	if (*ccase == LC)
	    return CONCISE_LC;		/* shift down */
	else
	    return CONCISE_UC;		/* shift up */
    }
    cc++;
    return c & CHARBITS;
}

/*
 * Function: flex
 * Synopsis: Handle data for "flexo" pseudo
 * handle upper case by doing shifts
 */

WORD32 flex()
{
    WORD32 w;
    int shift;
    int ccase;

    if (line[lexstart] == ' ')		/* always? */
	next(0);

    /* original version appears to take next 3 characters,
     * REGARDLESS of what they are (tab, newline, space?)!
     */
    w = 0;
    ccase = LC;				/* current case */
    for (shift = 12; shift >= 0; shift -= 6) {
	unsigned char c;
	if( lexstart >= maxcc )
	    break;

	c = line[lexstart];
	if (c == '\t' || c == '\n') {
	    if (ccase == LC)
		break;
	    c = CONCISE_LC;			/* shift down first */
	}
	else {
	    if (c > 0177) {		/* non-ascii */
		errorMessage( &illegal_character, lexstart );
		c = 0;
	    }

	    c = ascii_to_fiodec[c&0177];
	    if (c == BAD) {
		errorMessage( &illegal_character, lexstart );
		c = 0;
	    }

	    if (!(c & ccase)) {		/* char not in current case? */
		ccase ^= BC;		/* switch case */
		if (ccase == LC)
		    c = CONCISE_LC;	/* shift down */
		else
		    c = CONCISE_UC;	/* shift up */
	    }
	    else
		lexstart++;
	}
	w |= (c & CHARBITS) << shift;
    }
    /* error to get here w/ case == UC? nah. shift down could be next */
    return w;
} /* flex */

/*
 * Function: getChar
 * Synopsis: Handle data for "char" pseudo
 */

WORD32 getChar()
{
    unsigned char c, pos;

    if( cc >= maxcc )
	return 0;			/* XXX error? */
    pos = line[cc++];
    if (pos != 'l' && pos != 'm' && pos != 'r') {
	errorMessage( &illegal_character, lexstart );
	return 0;
    }

    if( cc >= maxcc )
	return 0;			/* XXX error? */

    c = line[cc++];
    if (c > 0177) {
	errorMessage( &illegal_character, lexstart );
	c = 0;
    }

    c = ascii_to_fiodec[c];
    if (c == BAD) {
	errorMessage( &illegal_character, lexstart );
	c = 0;
    }

    if (!(c & LC)) {			/* upper case only char? */
	c = CONCISE_UC;			/* take a shift up */
	cc--;				/* and leave char for next luser */
    }

    c &= CHARBITS;
    switch (pos) {
    case 'l': return c << 12;
    case 'm': return c << 6;
    case 'r': return c;
    }
    /* should not happen */
    return 0;
} /* flex */

/*  Function:  eval */
/*  Synopsis:  Get the value of the current lexeme, and advance.*/
SYM_T eval2()
{
  WORD32  digit;
  WORD32  from;
  SYM_T  *sym;
  WORD32  val;
  SYM_T   sym_eval;

  sym_eval.type = DEFINED;
  sym_eval.name[0] = '\0';
  sym_eval.val = sym_eval.xref_index = sym_eval.xref_count = 0;

  val = 0;

  if( isLexSymbol()) {
    sym = evalSymbol();
    if(!M_DEFINED( sym->type )) {
      if( pass == 2 )
        errorSymbol( &undefined_symbol, sym->name, lexstart );
      next(1);
      return( *sym );
    }
    else if( M_PSEUDO(sym->type) || M_EPSEUDO(sym->type)) {
      switch (sym->val) {
      case DECIMAL:
        radix = 10;
	sym_eval.type = PSEUDO;
	sym_eval.val = 0;		/* has zero as a value! */
	break;
      case OCTAL:
        radix = 8;
	sym_eval.type = PSEUDO;
	sym_eval.val = 0;		/* has zero as a value */
	break;
      case FLEX:
	next(1);			/* skip keyword */
	sym_eval.val = flex();
	break;
      case CHAR:
	next(1);			/* skip keyword */
	sym_eval.val = getChar();
	break;
      default:
        errorSymbol( &value_required, sym->name, lexstart );
        sym_eval.type = sym->type;
	sym_eval.val = 0;
	break;
      }
      next(1);
      return( sym_eval );
    }
    else if( M_MACRO( sym->type ))
    {
      if( pass == 2 )
      {
        errorSymbol( &misplaced_symbol, sym->name, lexstart );
      }
      sym_eval.type = sym->type;
      sym_eval.val = 0;
      next(1);
      return( sym_eval );
    }
    else
    {
      next(1);
      return( *sym );
    }
  } /* symbol */
  else if( isdigit( line[lexstart] )) {
    from = lexstart;
    val = 0;
    while( from < lexterm ) {
	if( isdigit( line[from] )) {
	    digit = line[from++] - '0';
	    if( digit >= radix ) {
		errorLexeme( &number_not_radix, from - 1 );
		val = 0;
		break;
	    }
	    val = val * radix + digit;
	}
	else {
	    errorLexeme( &not_a_number, lexstart );
	    val = 0;
	    break;
	}
    }
    next(1);
    sym_eval.val = val;
    return( sym_eval );
  } /* digit */
  else {
    switch( line[lexstart] ) {
    case '.':				/* Value of Current Location Counter */
	val = clc;
	next(1);
	break;
    case '(':				/* Generate literal */
	next(1);			/* Skip paren */
	val = getExprs();		/* recurse */
	if( line[lexstart] == ')' )
	    next(1);			/* Skip end paren */
	sym_eval.val = literal(val);
	return sym_eval;
    case '[':				/* parens!! */
	next(1);
	sym_eval.val = getExprs();	/* mutual recursion */
	if( line[lexstart] == ']' )
	    next(1);			/* Skip close bracket */
	else
	    errorMessage( &illegal_character, lexstart );
	return sym_eval;
    default:
	switch( line[lexstart] ) {
	case '=':
	    errorMessage( &illegal_equals, lexstart );
	    moveToEndOfLine();
	    break;
	default:
	    errorMessage( &illegal_character, lexstart );
	    break;
	} /* error switch */
	val = 0;				/* On error, set value to zero. */
	next(1);				/* Go past illegal character. */
    } /* switch on first char */
  } /* not symbol or number */
  sym_eval.val = val;
  return( sym_eval );
} /* eval2 */


SYM_T eval() {
    SYM_T sym;

    switch (line[lexstart]) {
    case '-':				/* unary - */
	next(1);
	sym = eval2();			/* skip op */
	sym.val ^= 0777777;
	break;
    case '+':				/* unary + */
	next(1);			/* skip op */
	/* fall */
    default:
	sym = eval2();
    }
    return sym;
}

/*  Function:  incrementClc */
/*  Synopsis:  Set the next assembly location.  Test for collision with */
/*             the literal tables. */
WORD32 incrementClc()
{
  clc = (( clc + 1 ) & ADDRESS_FIELD );
  return( clc );
} /* incrementClc */


/*  Function:  readLine */
/*  Synopsis:  Get next line of input.  Print previous line if needed. */
void readLine()
{
    BOOL    ffseen;
    WORD32  ix;
    WORD32  iy;
    char    inpline[LINELEN];

    /* XXX panic if nrepeats > 0 (if self-feeding, do the backup here?) */

    listLine();				/* List previous line if needed. */
    error_in_line = FALSE;		/* No error in line. */

    if(curmacro && *curmacro->mac_ptr == '\0') { /* end of macro? */
	struct macinv *mp;

	listed = TRUE;			/* Already listed. */

	/* Restore invoking line. */
	strcpy(line, curmacro->mac_line);
	cc = lexstartprev = curmacro->mac_cc; /* Restore cc. */
	maxcc = strlen( line );		/* Restore maxcc. */

	mp = curmacro->prev;		/* pop stack */
	free(curmacro);
	curmacro = mp;

	return;
    } /* end of macro */

    cc = 0;				/* Initialize column counter. */
    lexstartprev = 0;
    if( curmacro ) {			/* Inside macro? */
	char mc;

	maxcc = 0;
	do {

	    mc = *curmacro->mac_ptr++;	/* Next character. */
	    /* watch for overflow? how could it?? */
	    line[maxcc++] = mc;
	} while( !ISEND( mc ));		/* note: terminates on tab?! */
	line[maxcc] = '\0';
	listed = nomac_exp;
	return;
    } /* inside macro */

    lineno++;				/* Count lines read. */
    listed = FALSE;			/* Mark as not listed. */
 READ_LINE:
    if(( fgets( inpline, LINELEN - 1, infile )) == NULL ) {
	filix_curr++;			/* Advance to next file. */
	if( filix_curr < save_argc ) {	/* More files? */
	    fclose( infile );
	    if(( infile = fopen( save_argv[filix_curr], "r" )) == NULL ) {
		fprintf( stderr, "%s: cannot open \"%s\"\n", save_argv[0],
			save_argv[filix_curr] );
		exit( -1 );
	    }
	    list_title_set = FALSE;
	    goto READ_LINE;
	}
	else
	    end_of_input = TRUE;
    } /* fgets failed */

    ffseen = FALSE;
    for( ix = 0, iy = 0; inpline[ix] != '\0'; ix++ ) {
	if( inpline[ix] == '\f' ) {
	    if( !ffseen && list_title_set ) topOfForm( list_title, NULL );
	    ffseen = TRUE;
	}
	else
	    line[iy++] = inpline[ix];
    }
    line[iy] = '\0';

    /* If the line is terminated by CR-LF, remove, the CR. */
    if( line[iy - 2] == '\r' ) {
	iy--;
	line[iy - 1] = line[iy - 0];
	line[iy] = '\0';
    }
    maxcc = iy;				/* Save the current line length. */
} /* readLine */


/*  Function:  listLine */
/*  Synopsis:  Output a line to the listing file. */
void listLine()
/* generate a line of listing if not already done! */
{
  if( listfile != NULL && listed == FALSE )
  {
    printLine( line, 0, 0, LINE );
  }
} /* listLine */


/*  Function:  printPageBreak */
/*  Synopsis:  Output a Top of Form and listing header if new page necessary. */
void printPageBreak()
{
  if( page_lineno >= LIST_LINES_PER_PAGE )
         /*  ( list_lineno % LIST_LINES_PER_PAGE ) == 0 ) */
  {
    topOfForm( list_title, NULL );
  }
} /* printPageBreak */


/*  Function:  printLine */
/*  Synopsis:  Output a line to the listing file with new page if necessary. */
void printLine( char *line, WORD32 loc, WORD32 val, LINESTYLE_T linestyle )
{
  if( listfile == NULL )
  {
    save_error_count = 0;
    return;
  }

  printPageBreak();

  list_lineno++;
  page_lineno++;
  switch( linestyle )
  {
  default:
  case LINE:
    fprintf( listfile, "%5d                   ", lineno );
    fputs( line, listfile );
    listed = TRUE;
    break;

  case LINE_VAL:
    if( !listed )
    {
      fprintf( listfile, "%5d       %6.6o      ", lineno, val );
      fputs( line, listfile );
      listed = TRUE;
    }
    else
    {
      fprintf( listfile, "            %6.6o\n", val );
    }
    break;

  case LINE_LOC:
    if( !listed )
    {
      fprintf( listfile, "%5d %5.5o             ", lineno, loc );
      fputs( line, listfile );
      listed = TRUE;
    }
    else
    {
      fprintf( listfile, "      %5.5o\n", loc );
    }
    break;

  case LINE_LOC_VAL:
    if( !listed )
    {
      fprintf( listfile, "%5d %5.5o %6.6o      ", lineno, loc, val );
      fputs( line, listfile );
      listed = TRUE;
    }
    else
    {
      fprintf( listfile, "      %5.5o %6.6o\n", loc, val );
    }
    break;

  case LOC_VAL:
    fprintf( listfile, "      %5.5o %6.6o\n", loc, val );
    break;
  }
  printErrorMessages();
} /* printLine */


/*  Function:  printErrorMessages */
/*  Synopsis:  Output any error messages from the current list of errors. */
void printErrorMessages()
{
  WORD32  ix;
  WORD32  iy;

  if( listfile != NULL )
  {
    /* If any errors, display them now. */
    for( iy = 0; iy < save_error_count; iy++ )
    {
      printPageBreak();
      fprintf( listfile, "%-18.18s      ", error_list[iy].mesg );
      if( error_list[iy].col >= 0 )
      {
        for( ix = 0; ix < error_list[iy].col; ix++ )
        {
          if( line[ix] == '\t' )
          {
            putc( '\t', listfile );
          }
          else
          {
            putc( ' ', listfile );
          }
        }
        fputs( "^", listfile );
        list_lineno++;
        page_lineno++;
      }
      fputs( "\n", listfile );
    }
  }
  save_error_count = 0;
} /* printErrorMessages */


/*  Function:  punchObject */
/*  Synopsis:  Put one character to object file */
void punchObject( WORD32 val )
{
  val &= 0377;
  if( objectfile != NULL )
      fputc( val, objectfile );
} /* punchObject */

/*  Function:  punchTriplet */
/*  Synopsis:  Output 18b word as three 6b characters with ho bit set. */
void punchTriplet( WORD32 val )
{
  punchObject((( val >> 12) & 077) | 0200 );
  punchObject((( val >> 6 ) & 077) | 0200 );
  punchObject(( val & 077) | 0200 );
} /* punchTriplet */

void
eob() {
    /* in case no "start" in file (an error?) */
}

/*  Function:  punchLeader */
/*  Synopsis:  Generate 2 feet of leader on object file, as per DEC */
/*             documentation.  Paper tape has 10 punches per inch. */
void punchLeader( WORD32 count )
{
  WORD32  ix;

  /* If value is zero, set to the default of 2 feet of leader. */
  count = ( count == 0 ) ? 240 : count;

  if( objectfile != NULL )
  {
    for( ix = 0; ix < count; ix++ )
    {
      fputc( 0, objectfile );
    }
  }
} /* punchLeader */

/*  Function:  punchOutObject */
/*  Synopsis:  Output the current line and then then punch value to the */
/*             object file. */
void punchOutObject( WORD32 loc, WORD32 val )
{
  printLine( line, loc, val, LINE_LOC_VAL );
  punchLocObject( loc, val );
} /* punchOutObject */


/*  Function:  punchLocObjectRIM */
/*  Synopsis:  Output the word in RIM mode */
void punchLocObjectRIM( WORD32 loc, WORD32 val )
{
    punchTriplet( DIO | loc );
    punchTriplet( val );
} /* punchLocObject */

/* punch loader in RIM mode */
void
punchLoader() {
    int i;

    if (noinput)
	return;

    for (i = 0; i < DIM(loader); i++)
	punchLocObjectRIM(LOADERBASE+i, loader[i]);
    punchTriplet( JMP | LOADERBASE );
}

/*
 * flush out loader buffer; output a block:
 * DIO start
 * DIO end+1
 * .... data ....
 * sum
 */
#define PW(X) { WORD32 x = X; sum += x; punchTriplet(x); }
void
flushLoader() {
    WORD32 sum;
    int i;

    if (loaderbufcount == 0)
	return;

    sum = 0;
    PW( DIO | loaderbufstart );
    PW( DIO | loaderbufstart + loaderbufcount );
    for (i = 0; i < loaderbufcount; i++)
	PW( loaderbuf[i] );

    /* roll over all the overflows at once */
    if (sum & ~0777777)
	sum = (sum & 0777777) + (sum >> 18);
    if (sum & 01000000)			/* one more time */
	sum++;
    PW( sum );

    punchLeader(5);
    loaderbufcount = 0;
}

void punchLocObject( WORD32 loc, WORD32 val )
{
    if (!rim_mode) {
	if ((loc & LOADERBUFMASK) == 0 || /* full/force alignment */
	    loaderbufcount > 0 &&
	    loc != loaderbufstart + loaderbufcount) /* disjoint */
	    flushLoader();
	if (loaderbufcount == 0)
	    loaderbufstart = loc;
	loaderbuf[loaderbufcount++] = val;
    }
    else
	punchLocObjectRIM( loc, val );
}

/*  Function:  literal */
/*  Synopsis:  Add a value to the literal pool */
WORD32
literal( WORD32 value )
{
    int i;

    if (nconst >= MAX_CONSTANTS) {
	fprintf(stderr, "too many 'constants'; increase MAX_CONSTANTS\n");
	exit(1);
    }

    if (pass == 1) {
	if (++lit_count[nconst] == MAX_LITERALS) {
	    fprintf(stderr, "too many literals; increase MAX_LITERALS\n");
	    exit(1);
	}
	return lit_count[nconst];
    }

#if 1
    /*
     * pool constants; makes for a shorter tape
     * (but "middle" constants blocks can't shrink)
     */
    for (i = 0; i < nlit; i++)
	if (litter[i] == value)
	    return lit_loc[nconst] + i;
#endif

    /* paranoia */
    if (nlit == MAX_LITERALS) {
	fprintf(stderr, "too many literals; increase MAX_LITERALS\n");
	exit(1);
    }

    /* not found, save it */
    litter[nlit] = value;

    /* use base for this block, determined on pass1 */
    return lit_loc[nconst] + nlit++;
} /* literal */


/*  Function:  printSymbolTable */
/*  Synopsis:  Output the symbol table. */
/* XXX now prints FIXED symbols too */
void printSymbolTable()
{
    int    ix;
    int    symbol_lines;
    SYM_T *sym;
    char mark;

    symbol_lines = 0;
    for (ix = 0, sym = symtab; ix < symbol_top; ix++, sym++) {
	if (M_FIXED(sym->type) || M_PSEUDO(sym->type) ||
	    M_MACRO(sym->type) || M_EPSEUDO(sym->type))
	    continue;

	if (symbol_lines == 0) {
	    topOfForm( list_title, s_symtable );
	    symbol_lines = LIST_LINES_PER_PAGE;
	}

	switch( sym->type & ( DEFINED | REDEFINED )) {
	case UNDEFINED:
	    mark = '?';
	    break;

	case REDEFINED:
	    mark = '#';
	    break;

	default:
	    mark = ' ';
	    break;
	}
	fprintf( listfile, "%c%-6.6s %6.6o\n", mark, sym->name, sym->val );
	symbol_lines--;
    }
} /* printSymbolTable */


/*  Function:  printPermanentSymbolTable */
/*  Synopsis:  Output the permanent symbol table to a file suitable for */
/*             being input after the EXPUNGE pseudo-op. */
void printPermanentSymbolTable()
{
    int     ix;
    FILE   *permfile;

    if(( permfile = fopen( permpathname, "w" )) == NULL )
    {
	exit( 2 );
    }

    fprintf( permfile, "/ PERMANENT SYMBOL TABLE\n/\n" );
    fprintf( permfile, "        expunge\n/\n" );

    for( ix = 0; ix < symbol_top; ix++ )
    {
	int type = symtab[ix].type;
	if( M_FIXED(type) && !M_PSEUDO(type) && !M_EPSEUDO(type) )
	    fprintf( permfile, "\t%s=%o\n",
		     symtab[ix].name, symtab[ix].val );
    }
    fclose( permfile );
} /* printPermanentSymbolTable */


/*  Function:  printCrossReference */
/*  Synopsis:  Output a cross reference (concordance) for the file being */
/*             assembled. */
void printCrossReference()
{
    int    ix;
    int    xc;
    int    xc_index;
    int    xc_refcount;
    int    xc_cols;
    SYM_T  *sym;

    /* Force top of form for first page. */
    page_lineno = LIST_LINES_PER_PAGE;

    list_lineno = 0;

    for( ix = 0, sym = symtab; ix < symbol_top; ix++, sym++ ) {
	if (M_FIXED(sym->type) && xreftab[sym->xref_index] == 0)
	    continue;
	list_lineno++;
	page_lineno++;
	if( page_lineno >= LIST_LINES_PER_PAGE )
	    topOfForm( list_title, s_xref );

	fprintf( listfile, "%5d", list_lineno );

	/* Get reference count & index into concordance table for this symbol */
	xc_refcount = sym->xref_count;
	xc_index = sym->xref_index;
	/* Determine how to label symbol on concordance. */
	/* XXX flag variables? */
	switch( sym->type & ( DEFINED | REDEFINED )) {
	case UNDEFINED:
	    fprintf( listfile, " U         ");
	    break;

	case REDEFINED:
	    fprintf( listfile, " M  %5d  ", xreftab[xc_index] );
	    break;

	default:
	    fprintf( listfile, " A  %5d  ", xreftab[xc_index] );
	    break;
	}
	fprintf( listfile, "%-6.6s  ", sym->name );

	/* Output the references, 8 numbers per line after symbol name. */
	for( xc_cols = 0, xc = 1; xc < xc_refcount + 1; xc++, xc_cols++ ) {
	    if( xc_cols >= XREF_COLUMNS ) {
		xc_cols = 0;
		page_lineno++;
		if( page_lineno >= LIST_LINES_PER_PAGE )
		    topOfForm( list_title, s_xref);
		list_lineno++;
		fprintf( listfile, "\n%5d%-19s", list_lineno, " " );
	    }
	    fprintf( listfile, "  %5d", xreftab[xc_index + xc] );
	}
	fprintf( listfile, "\n" );
    } /* for */
} /* printCrossReference */


/*  Function:  topOfForm */
/*  Synopsis:  Prints title and sub-title on top of next page of listing. */
void topOfForm( char *title, char *sub_title )
{
    char temp[10];

    list_pageno++;
    strcpy( temp, s_page );
    sprintf( temp, "%s %d", s_page, list_pageno );

    if (!listfile)
	return;

    /* Output a top of form if not the first page of the listing. */
    if( list_pageno > 1 )
	fprintf( listfile, "\f" );

    fprintf( listfile, "\n      %-63s %10s\n", title, temp );

    /* Reset the current page line counter. */
    page_lineno = 1;
    if( sub_title != NULL )
    {
	fprintf( listfile, "%80s\n", sub_title );
	page_lineno++;
    }
    else
    {
	fprintf( listfile, "\n" );
	page_lineno++;
    }
    fprintf( listfile, "\n" );
    page_lineno++;
} /* topOfForm */


/*  Function:  lexemeToName */
/*  Synopsis:  Convert the current lexeme into a string. */
char *lexemeToName( char *name, WORD32 from, WORD32 term )
{
    int to;

    to = 0;
    while( from < term && to < SYMLEN-1) {
	char c = line[from++];
	if (ISOVERBAR(c))
	    continue;
	name[to++] = c;
    }
    name[to] = '\0';

    return( name );
} /* lexemeToName */

/*  Function:  defineLexeme */
/*  Synopsis:  Put lexeme into symbol table with a value. */
SYM_T *defineLexeme( WORD32  start,     /* start of lexeme being defined. */
		     WORD32  term,	/* end+1 of lexeme being defined. */
                     WORD32  val,       /* value of lexeme being defined. */
                     SYMTYP  type )     /* how symbol is being defined. */
{
    char  name[SYMLEN];

    lexemeToName( name, start, term);
    return( defineSymbol( name, val, type, start ));
} /* defineLexeme */


/*  Function:  defineSymbol */
/*  Synopsis:  Define a symbol in the symbol table, enter symbol name if not */
/*             not already in table. */
SYM_T *defineSymbol( char *name, WORD32 val, SYMTYP type, WORD32 start )
{
    SYM_T  *sym;
    WORD32  xref_count;

    if( strlen( name ) < 1 )
    {
	return( &sym_undefined );		/* Protect against non-existent names. */
    }
    sym = lookup( name, type );
    xref_count = 0;			/* Set concordance for normal defintion. */

    if( M_DEFINED( sym->type ) && sym->val != val && M_NOTRDEF( sym -> type ))
    {
	if( pass == 2 )
	{
	    errorSymbol( &redefined_symbol, sym->name, start );
	    type = type | REDEFINED;
	    sym->xref_count++;		/* Referenced symbol, count it. */
	    xref_count = sym->xref_count;
	    /* moved inside "if pass2" -plb 10/2/03 allow redefinition
	     * of predefined symbols during pass1
	     */
	    return ( sym );
	}
    }

    if( pass == 2 && xref )
    {
	/* Put the definition line number in the concordance table. */
	/* Defined symbols are not counted as references. */
	if (sym->xref_index >= 0) {	/* beware macro dummies */
	    xreftab[sym->xref_index] = lineno;
	    /* Put the line number in the concordance table. */
	    xreftab[sym->xref_index + xref_count] = lineno;
	}
    }

    /* Now set the value and the type. */
    sym->val = val & 0777777;
    sym->type = type;
    return( sym );
} /* defineSymbol */


/*  Function:  lookup */
/*  Synopsis:  Find a symbol in table.  If not in table, enter symbol in */
/*             table as undefined.  Return address of symbol in table. */
SYM_T *lookup( char *name, int type )
{
    int ix;				/* Insertion index */
    int lx;				/* Left index */
    int rx;				/* Right index */
    SYM_T *best;			/* best match */
    SYM_T *sym;

    /* YIKES!  Search dummies (and "R") before anything else!! */
    if (curmacro && curmacro->defn) {
	struct macdef *mdp = curmacro->defn;
	int i;

	for (i = 0, sym = mdp->args; i <= mdp->nargs; i++, sym++)
	    if (strcmp(name, sym->name) == 0)
		return sym;
    }

    lx = 0;
    rx = symbol_top - 1;
    best = NULL;
    while (lx <= rx) {
	int mx = (lx + rx) / 2;		/* Find center of search area. */
	int compare;

	sym = symtab + mx;

	compare = strcmp(name, sym->name);
	if (compare < 0)
	    rx = mx - 1;
	else if (compare > 0)
	    lx = mx + 1;
	else {				/* match */
	    if (overbar && !M_DEFINED(sym->type) && pass == 2) {
		sym->type = DEFINED;
		sym->val = vars_addr++;
		nvars++;
	    }
	    return sym;			/* return exact match */
	} /* match */

	/* save best non-exact match; MACRO returns last defined n-x match! */
	if ((M_PSEUDO(sym->type)||M_EPSEUDO(sym->type)||M_MACRO(sym->type)) &&
	    strncmp(name, sym->name, 3) == 0)
	    best = sym;
    } /* while */

    /* return best match (pseudo or macro) if any for lookups (not defns) */
    if (best && type == UNDEFINED)
	return best;

    /* Must put symbol in table if index is negative. */
    ix = lx;				/* insertion point */
    if( symbol_top + 1 >= SYMBOL_TABLE_SIZE ) {
	errorSymbol( &symbol_table_full, name, lexstart );
	exit( 1 );
    }

    for( rx = symbol_top; rx >= ix; rx-- )
	symtab[rx + 1] = symtab[rx];

    symbol_top++;

    /* Enter the symbol as UNDEFINED with a value of zero. */
    sym = symtab + ix;
    strcpy( sym->name, name );
    sym->type = UNDEFINED;
    sym->val  = 0;
    sym->xref_count = 0;
    if( xref && pass == 2 && sym->xref_index >= 0)
	xreftab[sym->xref_index] = 0;

    if (overbar)
	nvars++;

    return sym;
} /* lookup */

/*  Function:  compareSymbols */
/*  Synopsis:  Used to presort the symbol table when starting assembler. */
int compareSymbols( const void *a, const void *b )
{
    return( strcmp( ((SYM_T *) a)->name, ((SYM_T *) b)->name ));
} /* compareSymbols */

/*  Function:  evalSymbol */
/*  Synopsis:  Get the pointer for the symbol table entry if exists. */
/*             If symbol doesn't exist, return a pointer to the undefined sym */
SYM_T *evalSymbol()
{
    char   name[SYMLEN];
    SYM_T *sym;

    sym = lookup( lexemeToName( name, lexstart, lexterm ), UNDEFINED);

    sym->xref_count++;			/* Count the number of references to symbol. */

    if( xref && pass == 2 && sym->xref_index >= 0)
    {
	/* Put the line number in the concordance table. */
	xreftab[sym->xref_index + sym->xref_count] = lineno;
    }

    return( sym );
} /* evalSymbol */


/*  Function:  moveToEndOfLine */
/*  Synopsis:  Move the parser input to the end of the current input line. */
void moveToEndOfLine()
{
    while( !ISEND( line[cc] )) cc++;	/* XXX wrong! will stop on a tab! */
    lexstart = cc;
    lexterm = cc;
    lexstartprev = lexstart;
} /* moveToEndOfLine */

/* frame the next token in "line" with lexstart and lexterm indicies */
void
next(int op) {
    char c;

    /* Save start column of previous lexeme for diagnostic messages. */
    lexstartprev = lexstart;
    lextermprev = lexterm;

    c = line[cc];
    if (c == ' ') {
	/* eat spaces */
	do {
	    c = line[++cc];
	} while (c == ' ');
	if (op)				/* looking for operators? */
	    cc--;			/* return one */
    }

    overbar = 0;
    lexstart = cc;
    c = line[cc];
    if( isalnum(c) || ISOVERBAR(c)) {
	if (ISOVERBAR(c))
	    overbar = 1;
	do {
	    c = line[++cc];
	    if (ISOVERBAR(c))
		overbar = 1;
	} while (isalnum(c) || ISOVERBAR(c));
    }
    else if(!ISDONE(c) || c == '\t')	/* not end of line, or comment */
	cc++;				/* advance past all punctuation */
    lexterm = cc;
} /* next */

BOOL isLexSymbol()
{
    int ix;

    /* XXX alpha within first 4? 3?? */
    for( ix = lexstart; ix < lexterm; ix++ )
	if(isalpha(line[ix]))
	    return TRUE;			/* any position will do! */
    return FALSE;
} /* isLexSymbol */

/*
 * from macro manual (F-36BP), p.18;
 *
 * "A macro-instruction definition consists of four parts;
 * the pseudo-instruction _define_, the _macro instruction name_
 * amd _dummy symbol list,_ the _body_, and the pseudo-instruction
 * _terminate_.  Each part is followed by at least one tabulation or
 * carriage return."
 *
 * and in the next paragraph;
 *
 * "The name is terminated by a _space_ or by a _tab_ or _cr_
 * if there is no dummy symbol list."
 *
 * This accepts tabs and/or a newline after define
 * (but will accept a space), and only accepts spaces
 * between macro and dummy names.
 */

void
defineMacro() {
    int lexstartsave;			/* point to macro name */
    int index;				/* point to error char */
    int error;				/* error boolean */
    int i;
    int count;
    WORD32  length;
    WORD32  value;
    char    termin[SYMLEN];
    char    args[MAC_MAX_ARGS][SYMLEN];	/* macro & arg names */
    char    body[MAC_MAX_LENGTH + 1];
    struct macdef *mdp;
    SYM_T *sym;

    if (nrepeats) {
	/* we can call readLine, so throw up hands now */
	errorLexeme( &define_in_repeat, lexstartprev );
	return;
    }

    while (line[lexstart] == ' ' || line[lexstart] == '\t')
	next(0);

    /* not a tab or space */
    if (ISEND(line[lexstart])) {	/* newline or EOS? */
	/* crock; next token should invisibly skip over line boundaries? */
	readLine();
	next(0);
	while (line[lexstart] == ' ' || line[lexstart] == '\t')
	    next(0);
    }

    /* XXX pick up macro name out here */

    count = 0;
    index = 0;
    error = FALSE;
    lexstartsave = lexstart;
    while (!ISDONE(line[lexstart]) && count < MAC_MAX_ARGS) {
	if (!isalnum(line[lexstart]) && index == 0)
	    index = lexstart;		/* error pointer */
	lexemeToName( args[count++], lexstart, lexterm );
	/* XXX error if NOT a comma (& not first dummy) ? */
	if (line[lexterm] == ',')
	    next(0);			/* eat the comma */
	next(0);
	if (line[lexstart] == ' ')
	    next(0);
    }
    if( count == 0 ) {			/* No macro name. */
	errorMessage( &no_macro_name, lexstartsave );
	error = TRUE;
    }
    else if( index ) {			/* Bad argument name. */
	errorMessage( &bad_dummy_arg, index );
	error = TRUE;
    }
    else if( mac_count >= MAC_TABLE_LENGTH ) {
	errorMessage( &macro_table_full, lexstartsave );
	error = TRUE;
    }
    else {
	value = mac_count++;		/* sym value is index into mac */
	defineSymbol( args[0], value, MACRO, lexstartsave );
    }

    for( length = 0;; ) {
	readLine();
	if (end_of_input)
	    break;
	next(0);
	while (line[lexstart] == ' ' || line[lexstart] == '\t')
	    next(0);

	lexemeToName( termin, lexstart, lexterm ); /* just look at line? */
	if (strncmp( termin, "term", 4 ) == 0)
	    break;

	if (!error) {
	    int ll = strlen(line);
	    int allblank = FALSE;

	    /* don't save blank lines! */
	    for( i = 0; i < ll && allblank; i++ )
		if(!ISBLANK(line[i]))
		    allblank = FALSE;

	    if (allblank)			/* nothing but air? */
		continue;			/* skip it! */

	    if ((length + ll + 1) >= MAC_MAX_LENGTH ) {
		errorMessage (&macro_too_long, lexstart );
		error = TRUE;
		continue;
	    }

	    strcpy(body+length, line);
	    length += ll;
	}
    } /* for */
    if( error )
	return;

    mdp = calloc(1, sizeof(struct macdef) + length);
    if (mdp == NULL) {
	fprintf(stderr, "error allocating memory for macro definition\n");
	exit(1);
    }
    mac_defs[value] = mdp;

    strncpy(mdp->body, body, length);
    mdp->body[length] = '\0';
    mdp->nargs = count - 1;

    /*
     * save dummy names
     * symbol slot 0 reserved for "r" symbol
     * move SYM_T entries to macinv to allow recursion
     */
    sym = mdp->args;
    sym->type = DEFINED;
    strcpy(sym->name, "R");
    sym->val = 0;
    sym->xref_index = -1;		/* ??? allow xref? */
    sym++;

    for (i = 1; i <= mdp->nargs; i++, sym++) {
	sym->type = DEFINED;
	strcpy(sym->name, args[i]);
	sym->val = 0;
	sym->xref_index = -1;		/* don't xref!! */
    }
} /* defineMacro */

/* VARIABLES pseudo-op */
void
variables() {
    /* XXX error if "variables" already seen (in this pass) */
    /* XXX error if different address on pass 2 */
    if (pass == 2)
	printLine( line, clc, 0, LINE_LOC );
    vars_addr = clc;
    vars_end = clc = (clc + nvars) & ADDRESS_FIELD;
    if (pass == 2)
	printLine( line, clc, 0, LINE_LOC);
}

/* TEXT pseudo-op */
void
text(void)
{
    char delim;
    WORD32 w;
    int count;
    int ccase;
    /* XXX error in repeat!! */
    do {
	if (cc == maxcc) {
	    /* XXX EOL before delim found!!! */
	    fprintf(stderr, "FIX ME!\n");
	    return;
	}
	delim = line[cc++];
    } while (delim == ' ');		/* others? NL */

    w = count = 0;
    ccase = LC;
    for (;;) {
	int c = nextfiodec(&ccase, delim);
	if (c == -1)
	    break;
	w |= c << ((2-count)*6);
	if (++count == 3) {
	    punchOutObject(clc, w);	/* punch it! */
	    incrementClc();
	    count = w = 0;
	}
    }
    if (count > 0) {
	punchOutObject(clc, w);		/* punch remainder */
	incrementClc();
    }
}

/* CONSTANTS pseudo-op */
void
constants(void) {
    int i;

    /* XXX illegal inside macro (curmacro != NULL) */

    if (pass == 1) {
	lit_loc[nconst] = clc;

	/* just use addition?! */
	for (i = 0; i < lit_count[nconst]; i++)
	    incrementClc();

	nconst++;
	return;
    }

    /* pass 2: */
    /* XXX complain if clc != lit_base[nconst]? */

    for (i = 0; i < lit_count[nconst]; i++) {
	if (i < nlit)
	    punchOutObject( clc, litter[i] & 0777777); /* punch it! */
	incrementClc();
    }

    nconst++;
    nlit = 0;				/* litter[] now empty */
} /* constants */


/* process pseudo-ops
 * return FALSE if line scan should end (no longer used)
 */
BOOL pseudo( PSEUDO_T val )
{
    int count;
    int repeatstart;

    switch( (PSEUDO_T) val ) {
    case CONSTANTS:
	next(0);			/* Skip symbol */
	constants();
	break;

    case VARIABLES:
	next(0);			/* Skip symbol */
	variables();
	break;

    case DEFINE:
	next(0);			/* Skip symbol */
	defineMacro();
	return FALSE;
	break;

    case REPEAT:
	next(0);			/* Skip symbol */

	/* NOTE!! constant followed by SPACE picked up as expression!! */
	count = getExprs() & ADDRESS_FIELD;
	/* XXX error if sign bit set? */

	/* allow comma, but do not require */
	if( line[lexstart] == ',')
	    next(0);

	nrepeats++;
	repeatstart = lexstart;		/* save line start */
	while (count-- > 0) {
	    cc = repeatstart;		/* reset input pointer */
	    processLine();		/* recurse! */
	}
	cc = maxcc;
	nrepeats--;

	return FALSE;
	break;

    case START:
	next(0);			/* Skip symbol */
	/* XXX illegal in macro or repeat */
	flushLoader();
	if (!ISDONE(line[lexstart])) {
	    if (line[lexstart] == ' ')
		next(0);
	    start_addr = getExprs() & ADDRESS_FIELD;
	    next(0);
	    printLine( line, 0, start_addr, LINE_VAL );
	    /* MACRO punches 4" of leader */
	    punchTriplet(JMP | start_addr);
	    /* MACRO punches 24" of leader? */
	}
	/*
	 * handle multiple tapes concatenated into one file!!
	 * have command line option?? treat "start" as EOF??
	 */
	list_title_set = FALSE;
	return FALSE;

    case TEXT:
	/* NOTE!! no next()! */
	text();
	break;

    case NOINPUT:
	next(0);			/* Skip symbol */
	noinput = TRUE;
	break;

    case EXPUNGE:
	next(0);			/* Skip symbol */
	if (pass == 1)
	    init_symtab();
	break;

    default:
	break;
    } /* end switch for pseudo-ops */
    return TRUE;			/* keep scanning */
} /* pseudo */


/*  Function:  errorLexeme */
/*  Synopsis:  Display an error message using the current lexical element. */
void errorLexeme( EMSG_T *mesg, WORD32 col )
{
  char   name[SYMLEN];

  errorSymbol( mesg, lexemeToName( name, lexstart, lexterm ), col );
} /* errorLexeme */


/*  Function:  errorSymbol */
/*  Synopsis:  Display an error message with a given string. */
void errorSymbol( EMSG_T *mesg, char *name, WORD32 col )
{
  char   linecol[12];
  char  *s;

  if( pass == 2 )
  {
    s = ( name == NULL ) ? "" : name ;
    errors++;
    sprintf( linecol, ":%d:%d", lineno, col + 1 );
    fprintf( errorfile, "%s%-9s : error:  %s \"%s\" at Loc = %5.5o\n",
	    filename, linecol, mesg->file, s, clc );
    saveError( mesg->list, col );
  }
  error_in_line = TRUE;
} /* errorSymbol */


/*  Function:  errorMessage */
/*  Synopsis:  Display an error message without a name argument. */
void errorMessage( EMSG_T *mesg, WORD32 col )
{
  char   linecol[12];

  if( pass == 2 )
  {
    errors++;
    sprintf( linecol, ":%d:%d", lineno, col + 1 );
    fprintf( errorfile, "%s%-9s : error:  %s at Loc = %5.5o\n",
	    filename, linecol, mesg->file, clc );
    saveError( mesg->list, col );
  }
  error_in_line = TRUE;
} /* errorMessage */

/*  Function:  saveError */
/*  Synopsis:  Save the current error in a list so it may displayed after the */
/*             the current line is printed. */
void saveError( char *mesg, WORD32 col )
{
  if( save_error_count < DIM( error_list ))
  {
    error_list[save_error_count].mesg = mesg;
    error_list[save_error_count].col = col;
    save_error_count++;
  }
  error_in_line = TRUE;

  if( listed )
    printErrorMessages();
} /* saveError */

/* create a "symbol punch" for DDT */
/* MUST be called after object file closed; we reuse the FILE*! */

void
dump_symbols(void) {
    int ix;
    WORD32 addr;

    objectfile = fopen( sympathname, "wb" );
    if (!objectfile) {
	perror(sympathname);
	return;
    }

    punchLeader(0);
    punchLoader();
    punchLeader(5);

    /* XXX fudge addr -- get count, and subtract 2N from 07750? */
    addr = 05000;

    for( ix = 0; ix < symbol_top; ix++ ) {
	int i, type;
	WORD32 name;

	type = symtab[ix].type;
	if (M_FIXED(type) || M_PSEUDO(type) || M_MACRO(type))
	    continue;

	name = 0;
	for (i = 0; i < 3; i++) {
	    char c;

	    c = symtab[ix].name[i];
	    /* XXX leave on NUL? */

	    c = ascii_to_fiodec[tolower(c) & 0177];
	    /* XXX check for BAD entries? */

	    /* XXX OR in val<<(3-i)*6?? */
	    name <<= 6;
	    name |= c & CHARBITS;
	}
	punchLocObject(addr++, permute(name));
	punchLocObject(addr++, symtab[ix].val);
    }
    flushLoader();
    punchTriplet( JMP );		/* ??? */
    punchLeader(0);
    fclose(objectfile);
}
