/*

-------------------------------------------------------------

1) rpb()
2) is it JMP? If YES, -> done (file# ++)
3) is it DIO? If no, -er = scan until we get either JMP or DIO
4) if DIO then rpb() next word (there MUST be a next word)
5) if next word is ALSO DIO & addr > prv. addr then BIN else RIM

	-fr = force RIM on input (only needed if RIM file word 1 = DIO -- i.e. dio,dio)
//	-fb = force BIN on input (not needed)
	-fr force RIM input file
//	-fb force BIN input file

//-	-vp = verify parity?  Separate program for this!
		rpa() if 0200 then check parity -> separate program for this!

	-er = error recovery

	-sk = skip (implement)
	-sk n skip n blocks on input (block = up to JMP)

//	-bl = combined BIN loader + BIN output
//		leader + bin loader + gap + (if -ni clear 7751-7777 memory) + bin o/p
//		(same as -b + extra bin loader code in RIM format)

//	-ni = noinput (force 7751-7777 to be empty !!!!!
//	bool noinput = FALSE;

	always & 0770000 when checking against DIO/JMP (do not allow 'i')

	-er = error recovery
		= if block error (checksum etc. search for next DIO and ...
		  ... continue processing from there

//	exit_status -> set to EXIT_FAILURE if we get an error of some kind early on

//	-B = same as -b but APPEND
//	-R = sane as -r but APPEND

//	-bl = same as -b but includes a binary loader as a RIM prefix (recommend also use -ni)

//	-ls start line # (def=0)
//	-le end line # (def=999999)

//	long int fseek(FILE *f, long int offset, int whence) ->
//		whence=SEEK_SET (from beg of file) or SEEK_CUR (+/- n)
//	0=success, else fail!

Use THIS to check we are at the right line !!!!

	long int ftell(FILE *f) -> returns current pos in file
	returns -1L on fail, else curr pos




Usage:

Options:

Advanced options:

*/


/*
 * rimmer v0.0 - Process PDP-1 RIM files.	//$version#
 * Part of the  B L I N K Y - 1  project.
 *
 * Mike Hill.                16-Feb-2025.
 *
 */


        /**************************************************************\
        *                                                              *
        *         ,_    o    _  _  _     _  _  _     _    ,_           *
        *        /  |   |   / |/ |/ |   / |/ |/ |   |/   /  |          *
        *           |_/ |_/   |  |  |_/   |  |  |_/ |__/    |_/        *
        *                                                              *
        *                                                              *
        \**************************************************************/


/*
 * 'rimmer' was created as part of the BLINKY-1 project.
 *
 * It was named as a tribute to Arnold Rimmer (played by Chris Barrie) from
 * the BBC series "Red Dwarf".  If you haven't seen it, you haven't lived.
 *
 * BLINKY-1 came into life because I met Oscar Vermeulen and Angelo Papenhoff
 * at the Vintage Computer Forum in Zurich in January 2025, where Angelo asked
 * me if I had considered writing a BLINKY for their PDP-1 replica.  This is
 * the fourth BLINKY project after BLINKY-11 (for the PDP-11), BLINKY-8 (for
 * the PDP-8) and BLINKY-10 (for the PDP-10 and PDP-6).  Another stimulus was
 * that the PDP-1A was born in the same year I was.
 *
 * Why did I need a 'rimmer'?  It would be kind of 'nice' to be able to create
 * both plug-ins and drop-ins for BLINKY-1.  Plug-ins are bits of code which
 * can be called from BLINKY-1, but need to be optional.  Drop-ins are optional
 * bits of code which add value to BLINKY-1 but (usually) have no direct calls
 * from BLINKY-1.
 *
 * Examples of plug-ins for BLINKY-1 are the VF-Subsystem (Visual Frontpanel)
 * and SB-Subsystem (Sequence Break).  Examples of drop-ins are the
 * BL-Subsystem (Binary Loader) and ZM-Subsystem (Zero Memory).
 *
 * Also, I wanted to be able to create binary-load (BIN) files from the macro1
 * cross-assembler (which only creates RIM files).
 *
 * However, 'rimmer' got a bit out of hand.  It became a cornucopia of
 * functionality.  In addition to the planned '-b' and -o' outputs, it now
 * has a memory usage map (-u), a disassembler (-a or, with annotations, -A),
 * and a C-array source generator (-c).  Now it's well over 2000 lines long.
 *
 * Not only that, but I wanted to create something which would make life
 * easier for people who want to recover old PDP-1 paper-tapes.  That's why
 * I felt it necessary to put in a disassembler and memory-usage chart.
 *
 * 'rimmer' can also do things which the original PDP-1 developers didn't
 * think of.  For example, it can create a parity punch (-po and -pe) on
 * RIM and BIN files.  Back in the day, it might have been useful to be able
 * to verify a paper-tape down to the 'line' level.  Espcially given the
 * longevity of paper-tape, we might still like to do this today.
 *
 * 'rimmer' is also able to read more than one block of code from a single
 * paper-tape (-ra).  This is useful where a paper-tape contains a main program
 * followed by patches etc.  Normally, the PDP-1 loaders stop when they
 * encounter a 'jmp' instruction (because they 'jump') so you never see what
 * might follow on the paper-tape.
 *
 * 'rimmer' is very useful if a RIM or BIN file is convoluted.  For example,
 * some bits of code overwrite others.  The output created by 'rimmer' is
 * always sequential.  Only the 'last' bit of code is kept.  Overwritten
 * locations are 'lost'.
 *
 * All output files include an optional header (-h).  For RIM and BIN files,
 * this is prepended to the binary data in normal ASCII format.  For example,
 * on Microsoft Windows, you could TYPE the .rim or .bin file to see the header.
 *
 * You can create a single output file from multiple input files.  Punch all
 * the patches etc. with the main program onto one paper-tape.
 *
 * Each input file (paper-tape) is scanned for both RIM and BIN formats.
 * The correct format is automatically selected.  See what's going on
 * with verbose mode (-v).  It is possible to force RIM format (-fr) if
 * rimmer gets it wrong (which would require a very unusual RIM file to do).
 *
 * Output files may optionally skip large blocks of zero data (-z).  Useful
 * when an input file is filled with unwanted zeros.  Be careful not to skip
 * zeros which you really do want, though.
 *
 * To create a single paper-tape with, for example, a customary binary loader
 * followed by a program: first create a RIM file with the loader (-r), then
 * append the BIN file (-B) with the program.  Use the -pl, -pt, -pg, and -pb
 * options appropriately to set the amount of blank tape to punch.
 *
 * The output of '-o', '-a', and '-A' is designed to be assembled by macro1.
 * The output of '-u' is also in macro1 format, and may be added as a comment.
 * The output of '-c' is designed to be #included into a [4096] C-array.
 *
 * Start your journey with 'rimmer -?'.  This will show the full help text.
 *
 * Mike Hill, 16th February 2025.


********************************************************************************
 *
 * References:
 *    1) ...
 *    2) Description of BIN file ... pdp02_binLdr_oct61.pdf
 *
 *       https://simh.trailing-edge.com/sources/simtools/crossassemblers/macro1.c
 *       https://www.masswerk.at/spacewar/sources/macro1.c

vc++ versn
macro1.c
macro1 doc.
 */

/*

//$$$$$$$$$$$$$$$$$$$$$$$$
	-A  -> Annotate data in -a output (3 x FILODEC or -1 - -7777)
NO!	-ix -> include extensions along with the binary loader (zermem)
	-c  => generate C #include file for 4096 array
	-ra -> READ ALL continue reading input tape after 'jmp' to see if there is 'more'
	*** need to 'read-ahead' in 'rpb'
	*** add auto read of RIM/BIN format (look at first few words for dio,dio, ... ***

Syntax:

	rimmer [ options ] file [ file ... ]

	Files are loaded in the order given
	Any file may overwrite some (or all) of the locations loaded previously
	The last start address seen will be used by default as the output start

Options:

	-v		Verbose mode

	-s nnnnn	Defines the start address to be output

	-z n		Skip any blocks of at least 'n' zeros

	-r file		Output to 'file' in RIM format
	-b file		Output to 'file' in BIN format

NO!	-i		Include BIN loader at the start of the BIN file (-b)
	-t n		How much trailer / leader to punch
	-p		Punch odd parity in column 7 of the paper tape
	-g n		Gap to punch before/between/after code to paper tape
	-l n		Maximum Block length to punch before punching a gap

	-h "header"	Header on the paper tape and in the macro file (line 1)
	-d file		Write disassembled code to this file (macro1 format)
	-m file		Write octal code to this file (macro1 format)

	-D file         Write debgging output HERE

	n    = Decimal number
	nnnn = Octal number 0-7777

Defaults:



	-v	Quiet mode
	-s	Taken from input file(s) (defaults to 0003 if none found)
	-z	All data will be written (no zero skipping)
	-r	No RIM file written
	-b	No BIN file written
NO!	-i	No BIN loader will be written (only with -b)
	-p	No parity will be punched (column 7 always blank)
	-g	0 no intermediate gaps will be punched) (recommended = 5)
	-l	100 words
	-h	None
	-d	No disassembled code will be written
	-m	No octal code will be written

Thoughts:

	- read RIM file into memory (64kW)
	- All of memory init to -1 so we can see what we loaded and what we didn't
	- write new RIM file with all uninit words set to zero
	- write new RIM file exactly the same as the memory (with gaps) but without waste
	- dump RIM?
	- write BIN file
	- we could also punch a header text (e.g. first line of .mac file) in ASCII
	- all bytes with column 8 punched on the output tape may have col. 7 punched
	  steganography!
	- write .mac file in format:
		xxxx/	xxxxxx	xxxxxx	xxxxxx	xxxxxx	xxxxxx	xxxxxx
	- rimmer -r file.rim -b file.bin -f xxxxxx
		if -b then cut off 7751-7777?
		option for ranges to process?

*/



//#define RPB_ANY   (0)
//#define RPB_FATAL (1)

/*****************************************************************************/

/* Ignore security warnings in Microsoft C */

#ifdef _MSC_VER
#define _CRT_SECURE_NO_WARNINGS 1
#endif

/* Includes */

#include <ctype.h>
#include <errno.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>

/* Global Defines */

#ifndef FALSE
#define FALSE 0
#define TRUE (!FALSE)
#endif

#define MEMSIZE (010000)
#define EMPTY   (-1)
#define MINUS0  (0777777)
#define I_MASK  (0770000)
#define A_MASK  (0007777)
#define I_BIT   (0010000)

/* Typedefs */

typedef int word;    /* Used where data is coming from/going to memory[] */
typedef int bool;    /* Used where the value is either TRUE or FALSE */

/*****************************************************************************/

/* Define the FIODEC (unshifted) printable character set */

static const char fiodec[0100+1] =
     /* 00-07 */ " 1234567"
     /* 10-17 */ "89%%%%%%"
     /* 20-27 */ "0/stuvwx"
     /* 30-37 */ "yz%,%%%%"
     /* 40-47 */ "%jklmnop"
     /* 50-57 */ "qr%%-)%("
     /* 60-67 */ "%abcdefg"
     /* 70-77 */ "hi%.%%%%";

/*****************************************************************************/

/*
 * For paper tapes: Digial recommends 2 feet of leader tape (10 lines/inch).
 * For binary files: a 5 line gap after 100(8) written words is recommended.
 */

static word  start   = EMPTY;   /* Last 'start' address seen                 */
static word  memory[MEMSIZE];	/* Represents 4kW of 18-bit PDP-1 memory     */
static int   ptable[64];	/* Parity table to mask with binary data     */

/* Command line options and file names */

static bool  verbose = FALSE;	/* -v         Enable verbose mode            */
static word  startad = EMPTY;	/* -s nnnn    Set start address to nnnn      */
static char *header  = "";	/* -h "text"  Set header text to "text"      */

static bool  readall = FALSE;	/* -ra        Read all input tape's contents */
static int   skpfile = 0;	/* -sk n      Skip n files of RIM/BIN input  */
static int   zeroskp = MEMSIZE;	/* -z n       Skip blocks of 'this many' 0s  */

static bool  forcerim= FALSE;	/* -fr        Force file format to RIM       */
				/*            Use this if word 2 of RIM ...  */
				/*            ... file is a 'dio' > word 1   */

static int   parity  = 0;	/* -pn/po/pe  Punch no, odd, or even parity  */
static int   leader  = 240;	/* -pl n      Punch n lines of blank leader  */
static int   trailer = 48;	/* -pt n      Punch n lines of blank trailer */
static int   gap     = 5;	/* -pg n      Punch n lines of blank 'gap'   */
static int   blocksz = 0100;	/* -pb n      Punch a gap after n words o/p  */

static bool  recover = FALSE;	/* -er        Attempt error recovery         */	//?

static bool  annotate= FALSE;	/* -A  file   Annotate a '-a' file           */
static bool  rimapp  = FALSE;	/* -R  file   Append to the RIM file         */
static bool  binapp  = FALSE;	/* -B  file   Append to the BIN file         */
static bool  commas  = FALSE;	/* -O  file   Use comma-addresses for -o     */
static bool  noinput = FALSE;   /* -ni        Empties addresses 7751-7777    */
static bool  binldr  = FALSE;	/* -bl file   Binary (-b) with loader        */
static bool  nostart = FALSE;	/* -ns        Do not punch the START addr.   */

static char *rimout  = NULL;	/* -r file    Punch memory to RIM 'file'     */
static char *binout  = NULL;	/* -b file    Punch memory to BIN 'file'     */
static char *asmout  = NULL;	/* -a file    Disasemble memory to 'file'    */
static char *incout  = NULL;	/* -c file    Write C #include array to file */
static char *octout  = NULL;	/* -o file    Write memory to octal 'file'   */
static char *useout  = NULL;	/* -u file    Write memory usage to 'file'   */
static char *dbgout  = NULL;	/* -d file    Write debugging to 'file'      */

static int   begline = 0;	/* -ls n      Line (position) start-of-file  */
static int   endline = 99999;	/* -le n      Line (EOF) end-of-file         */

// Input file (paper-tape) context:

//- static int   filenum;	/* File number within the paper-tape (0-n)   */

static bool  eofseen;		/* Have we seen the EOF yet?                 */

static int   curfile;		/* //$ current file on this tape
		     = 0 on open and +1 @ first DIO & ??? at JMP/JSP*/
static bool  rimfile;		/* TRUE for RIM format, FALSE for BIN format */

//static bool  wantdio;		/* We are looking for a DIO opcode           */		//??
static int   curline;		/* //$ */
static int   binseen;		/* //$ */
static int   dioseen;		/* //$ */
const char  *tapename;		/* //$ */
FILE        *tapefile;		/* Used for all input (paper-tape) files     */

FILE *dbgfile        = NULL;	/* Used for the debug output file            */
FILE *outfile;			/* Used for all other output files           */

int exit_status = EXIT_SUCCESS;	/* Assume we will be successful              */

/*****************************************************************************/

/* Define the instructions used for 'dio' and 'jmp' (and even 'jsp') */

#define INST_DIO 0320000
#define INST_JMP 0600000
#define INST_JSP 0620000	/* Undocumented 'jmp' substutute in RIM/BIN */

/*****************************************************************************/

	/*************************************************************\
	 *  file_open() -- Open a file or exit with an error message *
	\*************************************************************/

FILE *file_open(const char *option, const char *filename, const char *mode) {
    FILE *handle = fopen(filename, mode);

    if (handle == NULL) {
        fprintf(stderr, "The '%s %s' could not be opened: %s\n",
                        option, filename, strerror(errno));
        exit(EXIT_FAILURE);
    }

    if (verbose)
        printf("Opening '%s %s' ...\n", option, filename);

    if (dbgfile)    /* Only use 'dbgfile' here: instead of 'dbgout' */
        fprintf(dbgfile, "Opening '%s %s' (%s) ...\n", option, filename, mode);

    return handle;
}

/*****************************************************************************/

	/**********************************************/
	/* tape_open() -- Open a paper-tape for input */
	/**********************************************/

void tape_open(const char *filename) {

    tapename = filename;
    tapefile = file_open("tape", tapename, "rb");

/* set all file values (pos etc.) to 0 etc. */

//	long int fseek(FILE *f, long int offset, int whence) ->
//		whence=SEEK_SET (from beg of file) or SEEK_CUR (+/- n)

    if (begline) {    /* -ls n */
        if (dbgout)
            fprintf(dbgfile, "Seeking to line %d ...\n", begline);
        if (fseek(tapefile, (long int) begline, SEEK_SET)) {
            fprintf(stderr, "'tape %s' could not be positioned to %d: %s\n",
                            begline, tapename, strerror(errno));
            exit(EXIT_FAILURE);
        }
    }

//?? fixup

    eofseen = FALSE;
    curfile = 0;
    curline = begline - 1;
    binseen = 0;
    dioseen = 0;
}

/*****************************************************************************/

	/***********************************************/
	/* rpa() -- Read the next line from paper-tape */
	/***********************************************/

static int  prevline = 0200;     /* Won't match the first time */
static bool ditto    = FALSE;

int rpa(void) {
    int line;

    if (curline >= endline)
        eofseen = TRUE;

    if (eofseen) {
        line = EOF;    /* Simulate EOF for -le */
    } else {
        line = fgetc(tapefile);
        if (line < 0) {
            eofseen = TRUE;
        } else {
            curline++;
            if (line & 0200) {
                binseen++;
            }
        }
    }

    if (dbgout && verbose) {
        if (line < 0) {
            fprintf(dbgfile, "Line#%5d) EOF\n", curline + 1);
            prevline = 0200;
            ditto    = FALSE;
        } else {
            if ((line & 0200) || line != prevline) {
                prevline = line;
                ditto = FALSE;
                if ((line & 0200) == 0 && isprint(line)) {
                    fprintf(dbgfile, "Line#%5d) %3.3o '%c'\n",
                                     curline, line, line);
                } else {
                    fprintf(dbgfile, "Line#%5d) %3.3o\n", curline, line);
                }
            } else {
                if (!ditto) {
                    fprintf(dbgfile, "Line#%5d)  \"\n",   curline);
                    ditto = TRUE;
                }
            }
        }
    }

    return line;
}

/*****************************************************************************/

#define OP_DIO 032	//???? move above here


	/**********************************************************/
	/* rpa_bin() -- Reads next binary line (column 8 punched) */
	/**********************************************************/

int rpa_bin(void) {
    int line;

/* allow EOF if no binary words read ... return EOF */
// binseen=1 means we have seen 1st binary line
// use WANTDIO if ERROR_RECOVERY ...

    do {
        do {
            line = rpa();
	    if (line < 0) {
                fprintf(stderr, "Unexpected end-of-file at line %d\n",
                                curline);
                if (binseen && !recover)
                    exit(EXIT_FAILURE);
                return 0377;    /* If we haven't seen any (more) data */
            }
        } while ((line & 0200) == 0);    /* Skip lines if col 8 not punched */

        line &= 077;    /* We're only interested in the 6 bits of payload */

        if (line == OP_DIO)
            dioseen++;

        if (dioseen)
            return line;    /* Return the payload */

        if (binseen == 1) {
            fprintf(stderr,
                    "Expected 32 (dio) but saw %2.2o at line %d\n",
                    line, curline);
        }
    } while (recover);

    exit(EXIT_FAILURE);    /* If we don't have -er then we die NOW */
}

/*****************************************************************************/

///////////??????????????????????

//int rpa_1st(void) {
//    int line;
//
//    for (;;) {
//        line = rpa_bin();
//        if ((line & I_MASK) == INST_DIO) break;
//        if ((line & I_MASK) == INST_JMP) break;
//        if ((line & I_MASK) == INST_JSP) break;
//
//}
//
//    return line;
//}

/*****************************************************************************/

//bool isrimfile(void) {
// rpb_1st() until we see DIO/JMP/JSP or EOF
// if -er else fail if first rpb
//    return FALSE;
//}

/*****************************************************************************/

word rpb(void) {
    int  lhs, mid, rhs;
    word data;


//    int  lhs  = rpa_bin();
//    int  cur  = curline;
//    int  mid  = rpa_bin();
//    int  rhs  = rpa_bin();
//    word data = (lhs << 12) | (mid << 6) | (rhs);

// need to keep 'cur' in context for file!!! -> rpbline ...
// move to CONTEXT (default=0)
static int rpbline;

    lhs     = rpa_bin();
    rpbline = curline;

    mid     = rpa_bin();
    rhs     = rpa_bin();

    data    = (lhs << 12) | (mid << 6) | (rhs);

    if (dbgout)
        fprintf(dbgfile, "Line#%5d] %6.6o\n", rpbline, data);

    return data;
}

/*****************************************************************************/

	/*******************************************\
	 * ppa() -- Punch a single 8-bit data byte *
	\*******************************************/

void ppa(int data) {
    if (fputc(data & 0377, outfile) == EOF)
        abort();
}

/*****************************************************************************/

	/********************************************\
	 * ppb() -- Punch a single 18-bit data word *
	\********************************************/

void ppb(word data) {
   int lhs = (data >> 12) & 077;
   int mid = (data >> 6)  & 077;
   int rhs = (data)       & 077;

   ppa (0200 | ptable[lhs] | lhs);
   ppa (0200 | ptable[mid] | mid);
   ppa (0200 | ptable[rhs] | rhs);
}

/*****************************************************************************/

	/******************************************************\
	 * punch_nulls() -- Punch a number of empty (0) lines *
	\******************************************************/

void punch_nulls(int count) {
    while (count-- > 0) {
        ppa(000);
    }
}

/*****************************************************************************/

	/******************************************\
	 * punch_ascii() -- Punch an ASCII string *
	\******************************************/

void punch_ascii(const char *string) {
    while (*string != '\0') {
        ppa(*string++ & 0177);
    }
}

/*****************************************************************************/

	/************************************************\
	 * Define the 'valid' first-binary-line opcodes *
	\************************************************/

#define OP_DIO 032
#define OP_JMP 060    /* Allowed, but 'jmp' w/o 'dio' would be surprising */	//???
#define OP_JSP 062    /* Allowed, but 'jsp' w/o 'dio' even more surprising */	//???

/*****************************************************************************/

// begin reading a tape ... moving to first 'file' in given format???

//word tape_begin(void) {
//    int line;
//
//    /* Read up to first binary line (column 8 punched) */
//
//    do {
//        line = rpa();
//    } while (!binseen && line != EOF);
//
//    if (line != OP_DIO && line != OP_JMP)
//        printf("NOT DIO && not JMP\n"); ///$
//
//    return line;	///$$$
//}

/*****************************************************************************/

/* load_rim() -- Load file from tape in RIM format */

void load_rim(word w1, word w2) {    /* w1 = 'dio' addr, w2 = data */
    for (;;) {
        if (curfile >= skpfile)
            memory[w1 & A_MASK] = w2;

        w1 = rpb();
        if ((w1 & I_MASK) != INST_DIO)
            break;

        w2 = rpb();
    }

    if ((w1 & I_MASK) == INST_JMP || (w1 & I_MASK) == INST_JSP) {
        if (curfile >= skpfile)
            start = w1 & A_MASK;
    } else {
//print filename, line, data read
        fprintf(stderr, "Bad data: %6.6o\n", w1);
    }
}

/*****************************************************************************/

/* load_bin() -- Load file from tape in BIN format */

void load_bin(word w1, word w2) {    /* w1 = 'dio' from, w2 = 'dio' to+1 */
    for (;;) {
        word from = w1 & A_MASK;
        word to   = w2 & A_MASK;
        word csum = w1 + w2;
        word data;

        if (csum > 0777777)
            csum = (csum + 1) & 0777777;

        for (; from < to; from++) {
            data  = rpb();
            csum += data;

            if (csum > 0777777)
                csum = (csum + 1) & 0777777;

            if (curfile >= skpfile)
                memory[from] = data;
        }

        data = rpb();
        if (data != csum) {
//print filename, line, data read
            fprintf(stderr, "Bad checksum: %6.6o\n", data);	//???
        }

        w1 = rpb();
        if ((w1 & I_MASK) != INST_DIO)
            break;

        w2 = rpb();
        if ((w2 & I_MASK) != INST_DIO || w2 <= w1) {
//print filename, line, data read
            fprintf(stderr, "Bad w2: %6.6o\n", w2);	//???
            exit(EXIT_FAILURE);
        }
    }

    if ((w1 & I_MASK) == INST_JMP || (w1 & I_MASK) == INST_JSP) {
        if (curfile >= skpfile)
            start = w1 & A_MASK;
    } else {
//print filename, line, data read
        fprintf(stderr, "Bad data: %6.6o\n", w1);
    }
}

/*****************************************************************************/
/*****************************************************************************/

	/*
	 * load_tape() -- ... //$$
	 */

/*
 * A paper tape consists of 1-n 'files'.
 * Each 'file' may be either RIM or BIN format [see load_bin_loader() below].
 * Each 'file' terminates with a JMP (although, any instruction which alters
 * the PC other than SKPs would be fine - e.g. cal, jda, jsp, or even xct of
 * a jmp etc.  We correctly handle JMP & JSP but not CAL or JDA (or XCT).
 *
 * Unless in -ra or -sk mode, eof = jmp/jsp x (either RIM or BIN).
 *
 * Autodetect RIM/BIN 'file' (override with -fr to force RIM format):
 *    'file RIM': word 1 = dio n, word 2 <> dio n+x
 *    'file BIN': word 1 = dio n, word 2 = dio n+x
 *    We do not allow a 'file' to start with 'jmp'.
 *
 */

void load_tape(const char *filename) {
    word w1, w2;

    tape_open(filename);

//	tape_begin() ...
//	determine file type (word 1 & word 2)
//	if word 1 = jmp/jsp we're done (file type = don't care)
//	else word 2 = dio > word 1 then BIN else RIM
//	-fr = force rim!!!

// reset input file context ... if no bin found then rpb() returns EMPTY for EOF

    binseen = 0;    /* Flag that we are looking for the start of a tape-file */

    w1 = rpb();
    if ((w1 & I_MASK) == INST_DIO) {
        w2 = rpb();
        if (forcerim || (w2 & I_MASK) != INST_DIO || w2 <= w1) {
            load_rim(w1, w2);
        } else {
            load_bin(w1, w2);
        }
    } else {
// do not allow JMP etc. as first word ?
    }

//    if (!binseen) {
//        fprintf(stderr, "No binary data found in %s\n", filename);
//    }

    if (fclose(tapefile) == EOF)
        perror("Failed to close file");
}

/*****************************************************************************/

void xxxload_tape(const char *filename) {
//    FILE *tape;
    word w1, w2, op;

    tape_open(filename);
//	tape_begin() ...
//	determine file type (word 1 & word 2)
//	if word 1 = jmp/jsp we're done (file type = don't care)
//	else word 2 = dio > word 1 then BIN else RIM
//	-fr = force rim!!!

    do {
        w1 = rpb();
        op = w1 >> 12;
        w1 = w1 & A_MASK;

//printf("%6.6o: %o\n",w1,op);

        if (op == 032) {    /* dio */
            w2 = rpb();
            memory[w1] = w2;
        } else if (op == 060) {    /* jmp */	//?? 062
            start = w1;
        } else {
            fprintf(stderr, "Bad data: %6.6o\n", w1);
        }
    } while (op == 032);

    if (!binseen) {
        fprintf(stderr, "No binary data found in %s\n", filename);
    }

    if (fclose(tapefile) == EOF)
        perror("Failed to close file");
}

/*****************************************************************************/

void usage(char *opt, char *param) {

    if (opt != NULL)
        printf("Bad option: %s %s\n", opt, (param==NULL) ? "" : param);

    printf("Usage:\n");
    printf("   rimmer [-?] [-v] [-ra] [-s nnnn|-ns] [-h \"text\"] [-z n]\n");
    printf("          [-pn | -po | -pe] [-pl n] [-pt n] [-pg n] [-pb n]\n");
    printf("          [-r file] [-b|-B|-bl file] [-a|-A file] [-c file] [-o|-O file]\n");
    printf("          [-d file]\n");
    printf("          input-file(s) ...\n");

    /* Fudge full help for '-h' */

    if (opt)
        if (opt[0] == '-' && opt[1] == 'h')
            opt = NULL;

    /* Full help if in verbose mode or we asked for it */

    if (verbose || opt == NULL) {
        printf("\n");
        printf("FULL TEXT ...\n");

/* One character options:
 *     -v -s -h -z -R -r -B -b -A -a -c -o -u -d */

/* Two character options:
 *     -bl -ns -le -ls -ni -pn -po -pe -pl -pt -pg -pb -ra -sk */

/*
	-?         Show full usage
	-v         Verbose mode
	-ra        Read all of the input tapes (continue reading after 'jmp')
	-s nnnn    Defines the start address to be output
	-h "text"  Header on the paper tape and in the macro file (line 1)

	-z n       Skip any blocks of at least 'n' zeros

	-pn        Punch no   parity in column 7 of the paper tape
	-po        Punch odd  parity in column 7 of the paper tape
	-pe        Punch even parity in column 7 of the paper tape
	-pl n      How much leader  to punch
	-pt n      How much trailer to punch
	-pg n      Gap to punch before/between/after code to paper tape
	-pb n      Maximum Block length to punch before punching a gap

	-ni        'noinput' = remove the binary loader from memory

	-r file    Output to 'file' in RIM format
	-R file    As -r but append instead of creating a new file
	-b file    Output to 'file' in BIN format
	-B file    As -b but append instead of creating a new file
	-a file    Write assembler code to this file (macro1 format)
	-A file    As '-a' but annotate data in -a output (3 x FILODEC or -ve number)
	-o file    Write octal code to this file (macro1 format)
	-d file    Write debgging output HERE

	n    = Decimal number
	nnnn = Octal number 0-7777
*/

    }

    if (opt == NULL)
        exit(EXIT_SUCCESS);
    exit(EXIT_FAILURE);
}

/*****************************************************************************/

char *gettext(char *opt, char *argv) {

    if (argv == NULL)
        usage(opt, "(EOL)");    /* EOL where parameter expected */

    if (argv[0] == '-')
        usage(opt, argv);       /* Parameter is missing */

    return argv;
}

/*****************************************************************************/

int getnum(char *opt, char *argv, int base, unsigned long int max) {
    unsigned long int retval;
    char *endptr;

    (void) gettext(opt, argv);    /* Validate the parameter really is there */

    retval = strtoul(argv, &endptr, base);
    if (*endptr != '\0' || retval >= max)
        usage(opt, argv);

    return (int) retval;
}

/*****************************************************************************/

int parse_args(int argc, char *argv[]) {
    char *parity_type = "noe";
    int   arg = 0;

    while (++arg < argc) {
        char *opt = argv[arg];

        /* Stop when we have no more options */

        if (opt[0] != '-')
            break;

        /* Options are one or two characters long */

        if (opt[1] == '\0')
            usage(argv[arg], NULL);    /* Empty option */

        if (opt[2] == '\0') {

            /* One character options:
             *     -v -s -h -z -R -r -B -b -A -a -c -o -u -d */

            switch (opt[1]) {
            case '?':          /* -? = Full help (usage) text */
                usage(NULL, NULL);
            case 'v':          /* -v = Verbose (also applies to -d) */
                verbose = TRUE;
                break;
            case 's':          /* -s = Start address */
                startad = getnum(opt, argv[++arg], 8, MEMSIZE);
                break;
            case 'h':          /* -h = Header text */
                header  = gettext(opt, argv[++arg]);
                break;
            case 'z':          /* -z = Skip blocks of zeros */
                zeroskp = getnum(opt, argv[++arg], 0, MEMSIZE);
                break;
            case 'R':          /* -R = Append to RIM file */
                rimapp  = TRUE;  /* Append to the RIM file */
                /* FALL THROUGH */
            case 'r':          /* -r = Write to RIM file */
                rimout  = gettext(opt, argv[++arg]);
                break;
            case 'B':          /* -B = Append to BIN file */
                binapp  = TRUE;  /* Append to the BIN file */
                /* FALL THROUGH */
            case 'b':          /* -b = Write to BIN file */
                binout  = gettext(opt, argv[++arg]);
                break;
            case 'A':          /* -A = Annotate ASM file */
                annotate= TRUE;
                /* FALL THROUGH */
            case 'a':          /* -a = Write to ASM file */
                asmout  = gettext(opt, argv[++arg]);
                break;
            case 'c':          /* -c = Write to C #include file */
                incout  = gettext(opt, argv[++arg]);
                break;
            case 'O':          /* -O = Write ',' addrsses to -o file */
                commas  = TRUE;
                /* FALL THROUGH */
            case 'o':          /* -o = Write to octal file */
                octout  = gettext(opt, argv[++arg]);
                break;
            case 'u':          /* -u = Write to memory usage file */
                useout  = gettext(opt, argv[++arg]);
                break;
            case 'd':          /* -d = Write to debug file */
                dbgout  = gettext(opt, argv[++arg]);
                break;
            default:
                usage(opt, NULL);
            }

        } else {

            /* Two character options:
             *     -bl -ns -fr -le -ls -ni -pn -po -pe -pl -pt -pg -pb -ra -sk */

            if (opt[3] != '\0')
                usage(opt, NULL);    /* More than two characters */

            switch (opt[1]) {
            case 'b':              /* -b? */
                switch (opt[2]) {
                case 'l':          /* -bl file = binary with loader */
                    binldr  = TRUE;
                    binout  = gettext(opt, argv[++arg]);
                    break;
                default:
                    usage(opt, NULL);
                }
                break;

            case 'e':              /* -e? */
                switch (opt[2]) {
                case 'r':          /* -er = Attempt error recovery */
                    recover = TRUE;
                    break;
                default:
                    usage(opt, NULL);
                }
                break;

            case 'f':              /* -f? */
                switch (opt[2]) {
                case 'r':          /* -fr = Force RIM format */
                    forcerim= TRUE;
                    break;
                default:
                    usage(opt, NULL);
                }
                break;

            case 'l':              /* -l? */
                switch (opt[2]) {
                case 'e':          /* -le = line_end */
                    endline = getnum(opt, argv[++arg], 10, 999999);
                    break;
                case 's':          /* -ls = line_start */
                    begline = getnum(opt, argv[++arg], 10, 999999);
                    break;
                default:
                    usage(opt, NULL);
                }
                break;

            case 'n':              /* -n? */
                switch (opt[2]) {
                case 'i':          /* -ni = no input (7751-7777 = EMPTY) */
                    noinput = TRUE;
                    break;
                case 's':
                    nostart = TRUE;
                    break;
                default:
                    usage(opt, NULL);
                }
                break;

            case 'p':              /* -p? */
                switch (opt[2]) {
                case 'n':          /* -pn = Parity: none */
                    parity  = 0;
                    break;
                case 'o':          /* -po = Parity: odd  */
                    parity  = 1;
                    break;
                case 'e':          /* -pe = Parity: even */
                    parity  = 2;
                    break;
                case 'l':          /* -pl = Punch leader */
                    leader  = getnum(opt, argv[++arg], 10, 1000);
                    break;
                case 't':          /* -pt = Punch trailer */
                    trailer = getnum(opt, argv[++arg], 10, 1000);
                    break;
                case 'g':          /* -pg = Punch gap */
                    gap     = getnum(opt, argv[++arg], 10, 100);
                    break;
                case 'b':          /* -pb = Punch block size */
                    blocksz = getnum(opt, argv[++arg], 0, MEMSIZE);
                    break;
                default:
                    usage(opt, NULL);
                }
                break;

            case 'r':              /* -r? */
                switch (opt[2]) {
                case 'a':          /* -ra = read-all */
                    readall = TRUE;
                    break;
                default:
                    usage(opt, NULL);
                }
                break;

            case 's':              /* -s? */
                switch (opt[2]) {
                case 'k':          /* -sk = Skip blocks of RIM and/or BIN */
                    skpfile = getnum(opt, argv[++arg], 10, 1000);
                    break;
                default:
                    usage(opt, NULL);
                }
                break;

            default:
                usage(argv[arg], NULL);
            }
        }
    }

    /* Show the options selected */

    if (verbose) {
        printf("Options:\n");
    /*  printf(    "    -v\n") */
        if (readall)
            printf("    -ra\n");
        if (start >= 00000)
            printf("    -s  %4.4o\n",  start);
        printf(    "    -h  \"%s\"\n", header);
        if (zeroskp < MEMSIZE)
            printf("    -z  %4.1d\n",  zeroskp);
        if (parity)
            printf("    -p%c\n",       parity_type[parity]);
        printf(    "    -pl %4.1d\n",  leader);
        printf(    "    -pt %4.1d\n",  trailer);
        printf(    "    -pg %4.1d\n",  gap);
        printf(    "    -pb %4.1d\n",  blocksz);
        if (rimout != NULL)
            printf("    -r  %s\n",     rimout);
        if (binout != NULL)
            printf("    -b  %s\n",     binout);
        if (asmout != NULL)
            printf("    -%c  %s\n",   (annotate) ? 'A' : 'a', asmout);
        if (octout != NULL)
            printf("    -o  %s\n",     octout);
        if (dbgout != NULL)
            printf("    -d  %s\n",     dbgout);
    }

    /* Verify co-dependent options */

    if (begline > endline) {
        fprintf(stderr, "-ls %d > -le %d\n", begline, endline);
        exit(EXIT_FAILURE);
    }

    /* Make sure we have at least one file */

    if (arg >= argc)
        usage("No input files", NULL);

    /* Return the pointer to the first input file */

    return arg;
}


//DONE FROM BOTTOM UP TO HERE!!!!!!!!!!!!!!!

/*****************************************************************************/

	/********************************************\
	 * empty() -- Empty part (or all) of memory *
	\********************************************/

void empty(int from, int to) {
    while (from <= to) {
        memory[from++] = EMPTY;
    }
}

/*****************************************************************************/

	/**************************************************************\
	 * load_bin_loader() -- Load the binary loader into 7751-7777 *
	\**************************************************************/

/*
 * Binary paper-tape format:
 *
 *	- Only lines with a hole punched in channel 8 are considered
 *	- Channel 7 is ignored
 *	- 3 lines of 6-bits make up 1 word (of 18 bits)
 *
 * For example: 442270 will appear on the paper tape as follows:
 *
 *	Channel-> 8 7 6 5 4 : 3 2 1
 *	Line 1    O   O     : O		/ Bits  0-5  = 44
 *	Line 2    O     O   :   O	/ Bits  6-11 = 22
 *	Line 3    O   O O O :		/ Bits 12-17 = 70
 *
 * After 'rpb', these lines are assembled into IO as 100 100 010 010 111 000
 *
 * The binary paper-tape loader will either already be in memory or loaded
 * via the PDP-1 hardware RIM loader.  The binary loader itself will often
 * precede the data to be loaded.  Unless 'noinput' is used in macro1, this
 * is what you'll get.
 *
 * A RIM format paper-tape consists of any number of word-pairs of the form:
 *
 *	dio ADDRESS
 *	DATAWORD
 *	:
 *
 * The final word (and, perhaps, only word) takes the simple form:
 *
 *	jmp START
 *
 * The binary paper-tape consists of any number of data blocks of the form:
 *
 *	dio FROM
 *	dio TO + 1
 *	:
 *	DATAWORDS (TO+1-FROM words)
 *	:
 *	CHECKSUM
 *
 * The CHECKSUM is the sum (1's complement) of all words from
 * 'dio FROM' to the last DATAWORD.  For TO = FROM (one DATAWORD)
 * there will be a total of four words (12 lines) on the paper-tape.
 * The CHECKSUM will be the sum of the three words preceding it.
 *
 * The final block (and, perhaps, only block) takes the simple form:
 *
 *	jmp START
 */

void load_bin_loader(void) {
    int  addr;
    word binldr[] = {
        0730002,    /* BLload,  rpb		/7751/ Read the 'dio FROM'   */
        0327760,    /*          dio   BLstor	/7752/ Deposit IO to BLstor  */
        0107760,    /*          xct   BLstor	/7753/ Execute it            */
        0327776,    /*          dio   BLcsum	/7754/ Deposit it as chksum  */
        0730002,    /*          rpb		/7755/ Read the 'dio TO+1'   */
        0327777,    /*          dio   BLlast	/7756/ Deposit it in BLlast  */
        0730002,    /* BLnext,  rpb		/7757/ Read loop ... nxt wrd */
        EMPTY  ,    /* BLstor,  xx		/7760/ Store it in cur addr  */
        0217760,    /*          lac i BLstor	/7761/ Load last word stored */
        0407776,    /*          add   BLcsum	/7762/ Add the checksum      */
        0247776,    /*          dac   BLcsum	/7763/ Store it back into cs */
        0447760,    /*          idx   BLstor	/7764/ Increment the cur adr */
        0527777,    /*          sas   BLlast	/7765/ Skip if we're at TO   */
        0607757,    /*          jmp   BLnext	/7766/ ... else get nxt word */
        0207776,    /*          lac   BLcsum	/7767/ Load the checksum     */
        0407777,    /*          add   BLlast	/7770/ Add in the last instr */
        0730002,    /*          rpb		/7771/ Read the expected cks */
        0327776,    /*          dio   BLcsum	/7772/ Overwtite the cur cks */
        0527776,    /*          sas   BLcsum	/7773/ Skip if both cks same */
        0760400,    /*          hlt		/7774/ ... else halt and ... */
        0607751,    /*          jmp   BLload	/7775/ Start reading nxt blk */
        EMPTY  ,    /* BLcsum,  000000		/7776/ Checksum              */
        EMPTY       /* BLlast,  000000		/7777/ 'dio TO+1'            */
    };

    for (addr = 07751; addr <= 07777; addr++) {
        memory[addr] = binldr[addr - 07751];
    }
}

/*****************************************************************************/

	/***********************************************************\
	 * rimout_block() -- Punch a block of memory in RIM format *
	\***********************************************************/

void rimout_block(int from, int to, int start) {
    int count = 0;

    while (from <= to) {
        if (memory[from] == EMPTY) {
            from++;                  /* Skip EMPTY locations */
        } else {
            ppb(INST_DIO | from);    /* dio addr */
            ppb(memory[from++]);     /*     data */
            if (++count > blocksz) {
                punch_nulls(gap);
                count = 0;
            }
        }
    }

    if (start != EMPTY) {
        if (!nostart) {
            ppb(INST_JMP | start);       /* jmp start */
        }
    }
}

/*****************************************************************************/

	/***********************************************************\
	 * rimout_gen() -- Punch the whole of memory in RIM format *
	\***********************************************************/

void rimout_gen(char *filename, bool append) {

    /* Open the output file */

    outfile = file_open((append) ? "-R" : "-r",
                        filename, (append) ? "ab" : "wb");

    /* Punch the header (if we have one) */

    if (*header) {
        punch_ascii(header);
        punch_ascii("\015\012\032\004");    /* CR LF ^Z ^D */
        if (!leader) {
            punch_nulls(1);                 /* Trail at least one blank line */
        }
    }

    /* Punch the leader, all of memory, and trailer */

    punch_nulls(leader);
    rimout_block(00000, 07777, start);
    punch_nulls(trailer);
    fclose(outfile);
}

/*****************************************************************************/

	/*************************************************\
	 * binout_block() -- Punch a single binary block *
	\*************************************************/

void binout_block(int from, int to) {
    word dio_from = INST_DIO | from;
    word dio_to   = INST_DIO | (to + 1);
    word checksum = dio_from;
    int  addr;

    ppb(dio_from);    /* dio 'from' addr */
    ppb(dio_to);      /* dio 'to+1' addr */
    checksum += dio_to;
    if (checksum > 0777777)
        checksum = (checksum + 1) & 0777777;

    for (addr = from; addr <= to; addr++) {
        if (memory[addr] == EMPTY) abort();    /* Sanity check */
        ppb(memory[addr]);
        checksum += memory[addr];
        if (checksum > 0777777)
            checksum = (checksum + 1) & 0777777;
    }

    ppb(checksum);
}

/*****************************************************************************/

	/***********************************************************\
	 * binout_gen() -- Punch the whole of memory in BIN format *
	\***********************************************************/

/*
 * Care must be taken with BIN files: the binary loader cannot handle
 * address 7777.  Also, unless addresses 7751-7775 contain the binary
 * loader itself, it does not make much sense to write them.  Of course,
 * if you are clever, you could write code which overwrites the loader itself.
 */

void binout_gen(char *filename, bool append) {
    int addr  = 00000;

    /* Open the output file (note that 'append' is allowed even WITH '-bl') */

    outfile = file_open((binldr) ? "-bl" : (append) ? "-B" : "-b",
                        filename, (append) ? "ab" : "wb");

    /* Punch the header (if we have one) */

    if (*header) {
        punch_ascii(header);
        punch_ascii("\015\012\032\004");    /* CR LF ^Z ^D */
        if (!leader) {
            punch_nulls(1);                 /* Trail at least one blank line */
        }
    }

    /* Punch the leader and, optionally, the binary-loader in RIM format */

    punch_nulls(leader);

    if (binldr) {

        load_bin_loader();
        {
            bool old_nostart = nostart;

            nostart = FALSE;
            rimout_block(07751, 07777, 07751 /* 07774 to HLT before loading */ );
            nostart = old_nostart;
        }
        if (noinput) {
            empty(07751, 07777);    /* We expect to empty the binary-loader */
        }
        punch_nulls(gap);
    }

    /* Punch memory from 0000-7776 in blocks of up to -pb words */

    do {
        for (; addr < 07777; addr++) {
            int from;

            if (memory[addr] != EMPTY) {
                for (from = addr; addr < 07777; addr++) {
                    if (memory[addr] == EMPTY) {
                        binout_block(from, addr - 1);
                        break;
                    }
                    if (addr - from > blocksz) {
                        binout_block(from, addr);
                        break;
                    }
                }
            }
        }
    } while (addr < 07777);

    /* Punch the 'start' address */

    if (start != EMPTY) {
        if (!nostart) {
            ppb(INST_JMP | start);       /* jmp start */
        }
    }

    /* Punch the trailer */

    punch_nulls(trailer);
    fclose(outfile);
}

/*****************************************************************************\
 *
 *		MACRO-1 Symbols (PDP-1 Instruction Set and Others)
 *		--------------------------------------------------
 *
 *	      MAIN	      SKIP	     SHIFT	      IOT
 *	  ------------	  ------------	  ------------	  ------------
 *	  and	020000	  skp	640000	  ral	661000	  iot	720000
 *	  ior	040000	  szf	640000	  ril	662000	  rpa	730001
 *	  xor	060000	  szs	640000	  rcl	663000	  rpb	730002
 *	  xct	100000	  sza	640100	  sal	665000	  tyo	730003
 *	  cal	160000	  spa	640200	  sil	666000	  tyi	720004
 *	  jda	170000	  sma	640400	  scl	667000	  ppa	730005
 *	  lac	200000	  szo	641000	  rar	671000	  ppb	730006
 *	  lio	220000	  spi	642000	  rir	672000	  dpy	730007
 *	  dac	240000	    INDIRECT	  rcr	673000	  rrb	720030
 *	  dap	260000	  ------------	  sar	675000	  cks	720033
 *	  dip	300000	  i	010000	  sir	676000	  lsm	720054
 *	  dio	320000	    OPERATE	  scr	677000	  esm	720055
 *	  dzm	340000	  ------------			  cbs	720056
 *	  add	400000	  opr	760000	  SHIFT AMOUNT	  lem	720074
 *	  sub	420000	  nop	760000	  ------------	  eem	724074
 *	  idx	440000	  clf	760000	  1s	000001
 *	  isp	460000	  stf	760010	  2s	000003	   UNDEFINED
 *	  sad	500000	  cla	760200	  3s	000007    ------------
 *	  sas	520000	  hlt	760400	  4s	000017	  szm	640500
 *	mus/mul	540000	  xx	760400	  5s	000037	  spq	650500
 *	dis/div	560000	  cma	761000	  6s	000077	  clo	651600
 *	  jmp	600000	  clc	761200	  7s	000177	  cfd	720074
 *	  jsp	620000	  lat	762200	  8s	000377	  cdf	720075
 *	  law	700000	  cli	764000	  9s	000777	  lap	760300
 *
\*****************************************************************************/

	/***********************************\
	 * opok[] = Array of valid opcodes *
	\***********************************/

#define NOO 000    /* NOO = Not a valid opcode */

static const int opok[040] = {

/*  ---, and, ior, xor, xct, ---, ---, ***,  */    /* (*** = cal or jda) */
    NOO, 002, 004, 006, 010, NOO, NOO, 016,

/*  lac, lio, dac, dap, dip, dio, dzm, ---,  */
    020, 022, 024, 026, 030, 032, 034, NOO,

/*  add, sub, idx, isp, sad, sas, mus, dis,  */
    040, 042, 044, 046, 050, 052, 054, 056,

/*  jmp, jsp, skp, SFT, law, iot, ---, opr   */    /* SFT is not a mnemonic */
    060, 062, 064, 066, 070, 072, NOO, 076

};

/*****************************************************************************/

	/**************************************\
	 * mnem[] = Array of opcode mnemonics *
	\**************************************/

static const char *mnem[040] = {
    NULL , "and", "ior", "xor", "xct", NULL , NULL , "cal",  /* (cal or jda) */
    "lac", "lio", "dac", "dap", "dip", "dio", "dzm", NULL ,
    "add", "sub", "idx", "isp", "sad", "sas", "mus", "dis",
    "jmp", "jsp", "skp", "SFT", "law", "iot", NULL , "opr"   /* SFT not mnem */
};

/*****************************************************************************/

	/*********************************************\
	 * Define the 'strange' opcodes (see opok[]) *
	\*********************************************/

#define OP_CAL 016    /* jda = 017 */
#define OP_SKP 064
#define OP_SFT 066    /* SFT is not a mnemonic */
#define OP_IOT 072
#define OP_OPR 076

/*****************************************************************************/

	/************************************************************\
	 * isopcode() -- Returns TRUE if the word is a valid opcode *
	\************************************************************/

bool isopcode(word data) {
    int op = opok[data >> 13];

    if (op == NOO) return FALSE ;

    switch (op) {
    case OP_CAL:    /* cal or jda */
        /* Make no distinction */  return TRUE;
    /*  break;  */
    case OP_SKP:    /* skp */
        if ((data & 0004000) == 0) return TRUE;
        break;
    case OP_SFT:    /* SFT */
        if ((data & 0007000) != 0) return TRUE;
        break;
    case OP_IOT:    /* iot */
    /*  if ((data & xx) == 0)  */  return TRUE;
    /*  break;  */
    case OP_OPR:    /* opr */
        if ((data & 0010060) == 0) return TRUE;
        break;
    default:
        return TRUE;
    }

    return FALSE;    /* If it ain't TRUE, it must be FALSE, Sherlock */
}

/*****************************************************************************/

	/***********************************************************\
	 * known_op() -- Returns the mnemonic for known operations *
	\***********************************************************/

const char *known_op(word data) {

    /* We do not interpret 'szf' or  'szs' */
    /* The same applies to 'clf' and 'stf' */
    /* These must all be done externally   */

    switch (data) {

    /* SKP */

    case 0640000: return "skp";
/*  case 0640000: return "szf";  */
/*  case 0640000: return "szs";  */
    case 0640100: return "sza";
    case 0640200: return "spa";
    case 0640400: return "sma";
/*  case 0640500: return "szm";  */
    case 0641000: return "szo";
    case 0642000: return "spi";

    /* SKP I */

    case 0650000: return "skp i";
/*  case 0650000: return "szf i";  */
/*  case 0650000: return "szs i";  */
    case 0650100: return "sza i";
    case 0650200: return "spa i";
    case 0650400: return "sma i";
/*  case 0650500: return "spq"  ;  */
    case 0651000: return "szo i";
/*  case 0651600: return "clo"  ;  */
    case 0652000: return "spi i";

    /* IOT */

    case 0720000: return "iot";
    case 0730001: return "rpa";
    case 0730002: return "rpb";
    case 0730003: return "tyo";
    case 0720004: return "tyi";
    case 0730005: return "ppa";
    case 0730006: return "ppb";
    case 0730007: return "dpy";
    case 0720030: return "rrb";
    case 0720033: return "cks";
    case 0720054: return "lsm";
    case 0720055: return "esm";
    case 0720056: return "cbs";
/*  case 0720056: return "lem";  */
/*  case 0720056: return "eem";  */
/*  case 0720074: return "cfd";  */
/*  case 0720075: return "cdf";  */

    /* OPR */

/*  case 0760000: return "opr";  */
    case 0760000: return "nop";
/*  case 0760000: return "clf";  */
/*  case 0760010: return "stf";  */
    case 0760200: return "cla";
/*  case 0760300: return "lap";  */
    case 0760400: return "hlt";
    case 0761000: return "cma";
    case 0761200: return "clc";
    case 0762200: return "lat";
    case 0764000: return "cli";
    }

    return NULL;    /* 'Unknown' operation */
}

/*****************************************************************************/

	/*************************************************************\
	 * sft_op() -- Returns the mnemonic for shift/rotate opcodes *
	\*************************************************************/

const char *sft_op(word data) {
    static char opc[4] = "SFT";
    const  char typ[5] = "Xaic";

    opc[0] = (data & 0004000) ? 's' : 'r';
    opc[1] = typ[(data & 0003000) >> 9];
    opc[2] = (data & I_BIT) ? 'r' : 'l';
/*  opc[3] = '\0';  */

    if (opc[1] != 'X')
        return opc;

    return NULL;
}

/*****************************************************************************/

	/******************************************************\
	 * asmout_gen() -- Generate the macro1 assembler file *
	\******************************************************/

void asmout_gen(const char *filename) {
    int  addr;
    int  last = MEMSIZE;     /* Force a mismatch the first time we use it */
    int  usedby[MEMSIZE];    /* What used this address?  (Prioritized)    */

#define USE_NONE  0000       /* Not used                                  */
#define USE_JMP   0001       /* Used to jump to [CAL, JDA, JMP, or JSP]   */
#define USE_READ  0002       /* Read from                                 */
#define USE_DEFER 0004       /* Used by a deferred instruction            */
#define USE_MOD   0010       /* Modified [IDX or ISP]                     */
#define USE_WRITE 0020       /* Written to [whole word]                   */
#define USE_DIP   0040       /* Written to instruction part specifically  */
#define USE_DAP   0100       /* Written to address     part specifically  */

    int usedfor[32] = {
        /* 00/01 --- */ USE_NONE,
        /* 02/03 AND */ USE_READ,
        /* 04/05 IOR */ USE_READ,
        /* 06/07 XOR */ USE_READ,
        /* 10/11 XCT */ USE_READ /* | don't USE_JMP */,
        /* 12/13 --- */ USE_NONE,
        /* 14/15 --- */ USE_NONE,
        /* 16/17 JDA */ USE_WRITE | USE_JMP,    /* 16=CAL, 17=JDA */
        /* 20/21 LAC */ USE_READ,
        /* 22/23 LIO */ USE_READ,
        /* 24/25 DAC */ USE_WRITE,
        /* 26/27 DAP */ USE_DAP /* | don't USE_WRITE */,
        /* 30/31 DIP */ USE_DIP /* | don't USE_WRITE */,
        /* 32/33 DIO */ USE_WRITE,
        /* 34/35 DZM */ USE_WRITE,
        /* 36/37 --- */ USE_NONE,
        /* 40/41 ADD */ USE_READ,
        /* 42/43 SUB */ USE_READ,
        /* 44/45 IDX */ USE_MOD /* | don't USE_READ | USE_WRITE */,
        /* 46/47 ISP */ USE_MOD /* | don't USE_READ | USE_WRITE */,
        /* 50/51 SAD */ USE_READ,
        /* 52/53 SAS */ USE_READ,
        /* 54/55 MUS */ USE_READ,   /* MUL */
        /* 56/57 DIS */ USE_READ,   /* DIV */
        /* 60/61 JMP */ USE_JMP,
        /* 62/63 JSP */ USE_JMP,
        /* 64/65 SKP */ USE_NONE,
        /* 66/67 sft */ USE_NONE,
        /* 70/71 LAW */ USE_NONE,
        /* 72/73 IOT */ USE_NONE,
        /* 74/75 --- */ USE_NONE,
        /* 76/77 OPR */ USE_NONE
    };

    /* Open the output file */

    outfile = file_open("-a", filename, "w");

    /* Output the heading */

    fprintf(outfile, "/%s\n", header);		/* TITLE LINE */
/*  fprintf(outfile, "\n");  */			/* EMPTY LINE */

    /* Initialize the 'used' table to 'empty' */

    for (addr = 00000; addr < MEMSIZE; addr++)
        usedby[addr] = USE_NONE << 12;

    /* Fill in the 'used' table */
    /* We store the LOWEST address in the order of 'aiwmdrj' */

#if 1
    for (addr = 00000; addr < MEMSIZE; addr++) {
        word data = memory[addr];
        int  used;

        if (data == EMPTY)
            continue;    /* Ignore EMPTY locations */

        used = usedfor[data >> 13];

        if (used == USE_NONE)
            continue;    /* Ignore locations which do not reference memory */

        if ((data & I_MASK) == 0160000)
            data = (data & I_MASK) | 00100;    /* CAL uses address 0100 */

        if (data & I_BIT)
            if ((data & I_MASK) != 0170000)
                used = USE_DEFER /* | don't USE_READ */;    /* Deferred (except JDA) */

        data = data & A_MASK;
        if (data == addr)
            continue;    /* Ignore self-referencing instructions */

        used = used << 12;
        if (used > usedby[data])
            usedby[data] = (usedby[data] & I_MASK) | addr;
        usedby[data] |= used;
    }

    /* Fill in the 'start' address */

    if (start >= 00000 && start < MEMSIZE)
        if (usedby[start])
            usedby[start] |= USE_JMP << 12;
        else
            usedby[start]  = USE_JMP << 12 | start;
#endif

    /* Output the body */

    for (addr = 00000; addr < MEMSIZE; addr++) {
        word data = memory[addr];

        if (data == EMPTY) {
            if (usedby[addr] == USE_NONE)
                continue;    /* Ignore 'unused' EMPTY locations */

            /* Always output the address for 'used' EMPTY locations */

            if (addr != ++last)
                fprintf(outfile, "\n");    /* Only if not contiguous */
            fprintf(outfile, "%4.4o/\t", addr);
            fprintf(outfile, "\t\t/ %4.4o/       ", addr);
            last = addr;
        } else {

            /* Output the new address if we skipped some location(s) */

            if (addr != ++last) {
                fprintf(outfile, "\n%4.4o/\t", addr);
                last = addr;
            } else if (usedby[addr]) {
                if (usedby[addr] & (USE_JMP << 12))
                    if ((usedby[addr] & A_MASK) != addr - 1)
                        fprintf(outfile, "\n");
                fprintf(outfile, "%4.4o,\t", addr);
            } else {
                fputc('\t', outfile);
            }

            /* Unknown opcodes and known opcodes are easiest */

            if (!isopcode(data)) {
                fprintf(outfile, "%6.6o  \t", data);
            } else {
                const char *ko = known_op(data);

                if (ko) {
                    fprintf(outfile, "%s     \t", ko);
                } else {
                    int         op = opok[data >> 13];
                    const char *mn = mnem[data >> 13];
                    char        i  = (data & I_BIT) ? 'i' : ' ';

                    /* Process all valid but not 'known' opcodes */

                    switch (op) {
                    case OP_SKP:    /* skp */

                        /* Process 'szs' & 'szf' */

                        if ((data & 0007700) == 0) {
                            if ((data & 0000070) && (data & 0000007)) {
                                /* Can't handle BOTH szs AND szf */
                            } else {
                                if (data & 0000007) {
                                    fprintf(outfile, "szf %c %1.1o \t",
                                                     i, data & 0000007);
                                } else {
                                    fprintf(outfile, "szs %c %2.2o\t",
                                                     i, data & 0000070);
                                }
                                break;
                            }
                        }
                        fprintf(outfile, "%s %c %4.4o\t", mn, i, data & A_MASK);
                        break;

                    case OP_SFT:    /* SFT */
                        {
                            const char *shift;
                            char shift_bits[4];

                            switch (data & 0000777) {
                            case 0001: shift = "1s"; break;
                            case 0003: shift = "2s"; break;
                            case 0007: shift = "3s"; break;
                            case 0017: shift = "4s"; break;
                            case 0037: shift = "5s"; break;
                            case 0077: shift = "6s"; break;
                            case 0177: shift = "7s"; break;
                            case 0377: shift = "8s"; break;
                            case 0777: shift = "9s"; break;
                            default:
                                {
                                    sprintf(shift_bits, "%3.3o", data & 0000777);
                                    shift = shift_bits;
                                }
                            }

                            fprintf(outfile, "%s   %s\t",
                                             sft_op(data),    /* NULL not possible */
                                             shift);
                            break;
                        }

                    case OP_OPR:    /* opr */

                        /* Process 'stf' & 'clf' */

                        if ((data & 0007760) == 0) {
                            if (data & 0000010) {
                                fprintf(outfile, "stf   %1.1o \t", data & 0000007);
                            } else {
                                fprintf(outfile, "clf   %1.1o \t", data & 0000007);
                            }
                            break;
                        }

                        fprintf(outfile, "%s  %5.4o\t", mn, data & 017777);
                        break;

                    case OP_CAL:    /* cal or jda */
                        if ((data & I_MASK) == 0170000)
                            mn = "jda";
                        i = ' ';
                        /* FALL THROUGH */

                    case OP_IOT:    /* iot */
                        /* FALL THROUGH */

                    default:
                        fprintf(outfile, "%s %c %4.4o\t", mn, i, data & A_MASK);
                    }
                }
            }

            fprintf(outfile, "/ %4.4o/ %6.6o", addr, data);
        }

        /* Output the used-by information */

        if (usedby[addr]) {
            int used = usedby[addr] >> 12;
            char type[7] = "------";

            if (used & USE_JMP)   type[0] = 'j';
            if (used & USE_READ)  type[1] = 'r';    /* 'r' will be ...       */
            if (used & USE_DEFER) type[1] = 'd';    /* ... overridden by 'd' */
            if (used & USE_MOD)   type[2] = 'm';
            if (used & USE_WRITE) type[3] = 'w';
            if (used & USE_DIP)   type[4] = 'i';
            if (used & USE_DAP)   type[5] = 'a';
            switch (addr - (usedby[addr] & A_MASK)) {
            case -1:
                fprintf(outfile, " ( .+1/%s)", type);
                break;
            case  0:
                if (used == USE_JMP && addr == start)
                    fprintf(outfile, " (start  %4.4o)", start);
                else
                    fprintf(outfile, " (   ./%s)", type);
                break;
            case +1:
                fprintf(outfile, " ( .-1/%s)", type);
                break;
            default:
                fprintf(outfile, " (%4.4o/%s)", usedby[addr] & A_MASK, type);
            }
        }

        /* Annotate the data word */

        if (annotate) {    /* FIODEC if >= 0100 and not -0 */
            if (data > 077 && data != MINUS0) {
                char lhs = fiodec[(data >> 12)      ];
                char mid = fiodec[(data >> 6)  & 077];
                char rhs = fiodec[(data)       & 077];

                if (lhs != '%' && mid != '%' && rhs != '%')
                    fprintf(outfile, "  \"%c%c%c\"", lhs, mid, rhs);
                else
                    if (data > MINUS0-1 - 999)    /* -1 to -999 (10) */
                        fprintf(outfile, "  -%o", ~data & 0377777);
            }
        }

        fputc('\n', outfile);
    }

    /* Output the trailer */

    fprintf(outfile, "\n");			/* EMPTY LINE */
    if (noinput)
        fprintf(outfile, "noinput\n");		/* NOINPUT LINE */
    fprintf(outfile, "start %4.4o\n", start);	/* START LINE */

    /* Close the output file */

    fclose(outfile);
}

/*****************************************************************************/

	/*************************************************\
	 * incout_gen() -- Generate the C #include array *
	\*************************************************/

void incout_gen(const char *filename) {
    int addr;

    /* Open the output file */

    outfile = file_open("-c", filename, "w");

    /* Output the heading */

    if (*header) {
        fprintf(outfile, "/* %s */\n", header);    /* TITLE LINE */
        fprintf(outfile, "\n");                    /* EMPTY LINE */
    }

    /* Output the body */

    for (addr = 00000; addr < MEMSIZE; addr += 8) {
        int i;

        fprintf(outfile, "/* %4.4o */  ", addr);

        for (i = addr; i < addr+8; i++) {
            word data = memory[i];

            if (data == EMPTY)
                data = 0000000;

            fprintf(outfile, "0%6.6o%s", data,
                    (i < MEMSIZE-1) ? "," : "");

        };
        fprintf(outfile, "\n");
    }

    /* Output the trailer */

    fprintf(outfile, "\n");                                  /* EMPTY LINE */
    fprintf(outfile, "#define START_ADDRESS 0%4.4o\n", start);    /* START */

    /* Close the output file */

    fclose(outfile);
}

/*****************************************************************************/

	/*********************************************************\
	 * outoct_gen() -- Generate the macro1 OCTAL source file *
	\*********************************************************/

void octout_gen(const char *filename) {
    int   addr;
    int   last = MEMSIZE;
    char *tabs = "\t\t\t\t\t\t\t\t";    /* 8 tabs */

    /* Open the output file */

    outfile = file_open((commas) ? "-O" : "-o", filename, "w");

    /* Output the heading */

    fprintf(outfile, "/%s\n", header);		/* TITLE LINE */
    fprintf(outfile, "\n");			/* EMPTY LINE */

    /* Output the body */

    for (addr = 00000; addr < MEMSIZE; addr += 8) {
        int  i, j = 0;

        do {
            for (i = j; i < 8; i++)
                if (memory[addr+i] != EMPTY)
                    break;

            if (i > 7)
                break;

            fprintf(outfile, "%4.4o%c%.*s", addr+i,
                    (commas && last == addr+i-1) ? ',' : '/', i, tabs);

            for (j = i; j < 8; j++)
                if (memory[addr+j] != EMPTY) {
                    last = addr+j;
                    fprintf(outfile, "\t%6.6o", memory[last]);
                } else {
                    break;
                }

            fprintf(outfile, "\n");
        } while (j < 8);
    }

    /* Output the trailer */

    fprintf(outfile, "\n");                      /* EMPTY   LINE */
    if (noinput)
        fprintf(outfile, "noinput\n");           /* NOINPUT LINE */
    fprintf(outfile, "start %4.4o\n", start);    /* START   LINE */

    /* Close the output file */

    fclose(outfile);
}

/*****************************************************************************/

	/******************************************************************\
	 * isfiodec() -- TRUE if word contains 2-3 non-space FIODEC chars *
	\******************************************************************/

bool isfiodec(word data) {
   char lhs = fiodec[(data >> 12)      ];
   char mid = fiodec[(data >>  6) & 077];
   char rhs = fiodec[(data      ) & 077];

   if (lhs == '%' || mid == '%' || rhs == '%')
      return FALSE;    /* Return FALSE if ANY character is non-FIODEC */

   return (lhs > ' ' && mid > ' ') ||
          (mid > ' ' && rhs > ' ') ||
          (lhs > ' ' && rhs > ' ');
}

/*****************************************************************************/

	/***************************************************\
	 * useout_gen() -- Generate the memory USAGE chart *
	\***************************************************/

void useout_gen(const char *filename) {
    int   addr = 00000;
    char *lgnd =
          ". = Empty,  0 = 0,  X = -0,  o = Opcode,  f = FIODEC,  x = Other";

    /* Open the output file */

    outfile = file_open("-u", filename, "w");

    /* Output the heading */

    if (*header) {
        fprintf(outfile, "/%s\n", header);    /* TITLE LINE */
        fprintf(outfile, "\n");               /* EMPTY LINE */
    }

    /* Output the body */

    do {
        fprintf(outfile, "/%4.4o-%4.4o/  ", addr, addr + 077);
        do {
            char c = 'x';
            word m = memory[addr];

            if (m == EMPTY)      c = '.';
            if (m == 000000)     c = '0';
            if (m == MINUS0)     c = 'X';
            if (c == 'x')
                if (isopcode(m)) c = 'o';
            if (c == 'x')
                if (isfiodec(m)) c = 'f';
            fputc(c, outfile);
        } while (++addr & 077);
        fputc('\n', outfile);
    } while (addr < MEMSIZE);

    /* Output the trailer */

    fprintf(outfile, "/ Legend: /  %s\n", lgnd);    /* LEGEND     */
    fprintf(outfile, "\n");                         /* EMPTY LINE */
/*  fprintf(outfile, "/ END-OF-FILE\n");  */        /* EOF   LINE */

    /* Close the output file */

    fclose(outfile);
}

/*****************************************************************************/

	/*************************************************************\
	 * parity_gen() - Generate the 6-bit parity 'or' mask table  *
	\*************************************************************/

void parity_gen(void) {
    int i;
    int mask = (parity) ? 7 : 0;

    /* Count the number of '1' bits for each of the 64 possible values */

    ptable[0] = 0;
    for (i = 0; i < 64; i++)
        ptable[i] = mask & ((i & 1) + ptable[i >> 1]);

    /* Fill in the table with either 0000 or 0100 (column 7 punched) */

    for (i = 0; i < 64; i++)
        ptable[i] = ((ptable[i] ^ parity) & 1) << 6;

    /* Debug the parity table */

    if (dbgout && verbose) {
        fprintf(dbgfile, "PARITY = \"");
        for (i = 0; i < 64; i++)
            fputc((ptable[i]) ? 'o' : '.', dbgfile);
        fprintf(dbgfile, "\"\n");
    }
}

/*****************************************************************************/

	/**************************\
	 * main() -- Main program *
	\**************************/

int main(int argc, char *argv[]) {
    int addr;         /* Address within memory (0000-7777)                 */
    int fnum;         /* Input file number within argv[]                   */
    int words = 0;    /* Number of words in memory after loading all tapes */

    /* EMPTY the whole of memory */

    empty(00000, 07777);    /* memory[00000..07777] = EMPTY; */

    /* Parse the argument list */

    fnum = parse_args(argc, argv);

    /* Open the debug file (if requested) */

    if (dbgout) {
        dbgfile = file_open("-d", dbgout, "w");
        fprintf(dbgfile, "rimmer - Debugging %s\n\n", header);
    }

    /* Log the command line (verbose only) */

    if (dbgout && verbose) {
        int i = 0;

        fprintf(dbgfile, "Command line:\n   ");
        while (i < argc) {
            if (i >= fnum || *argv[i] == '-') {
                fprintf(dbgfile, "\n   ");
            }
            fprintf(dbgfile, "%s ", argv[i++]);
        }
        fprintf(dbgfile, "\n\n");
    }

    /* Debug the FIODEC validation table */

    if (dbgout && verbose) {
        fprintf(dbgfile, "FIODEC = \"%s\"\n", fiodec);
    }

    /* Generate the parity table: [0..63] with 0 or 0100 */

    parity_gen();

    /* Load all the input files (paper-tapes) into memory */

    do {
        load_tape(argv[fnum++]);
    } while (fnum < argc);

    /* Remove blocks of zeros (if requested) */

    if (zeroskp < MEMSIZE) {
        int count = 0;

        for (addr = 00000; addr < MEMSIZE; addr++) {
            if (memory[addr] == 000000) {
                int sad;

                for (sad = addr; addr < MEMSIZE; addr++) {
                    if (memory[addr] != 000000) {
                        break;
                    }
                }
                if (addr-sad >= zeroskp) {
                    do {
                        memory[sad++] = EMPTY;
                        count++;
                    } while (sad < addr);
                }
            }
        }

        if (verbose) {
            printf("%d zeros have been removed\n", count);
        }
    }

    /* Remove the binary loader locations (if '-ni' requested) */

    if (noinput) {
        empty(07751, 07777);
        if (start >= 07751) {
            start = EMPTY;    /* In case we 'start' in the binary loader */
        }
    }

    /* Make sure we have a valid 'start' address */

    if (startad != EMPTY) {
        start = startad;    /* Always valid, even if -ni was specified */
    } else if (start == EMPTY) {
        for (start = 00000; start < MEMSIZE; start++) {
            if (memory[start] == 0760400) {
                break;  /* Break at the first pure 'hlt' op we see */
            }
        }

        if (start >= MEMSIZE) {
           start = 0004;    /* Pick something, ANYTHING, just PICK! */
        }

        fprintf(stderr, "No start address seen, %4.4o has been selected\n",
                        start);

        exit_status = EXIT_FAILURE;
    }

    /* Find out how much memory is utilized */

    for (addr = 00000; addr < MEMSIZE; addr++) {
        if (memory[addr] != EMPTY) {
            words++;
        }
    }

    if (verbose) {
        printf("Memory contains %d words (%d%%)\n",
               words, words * 100 / MEMSIZE);
    }

    /* If there is anything in memory, generate the output file(s) */
    /* (Although ... a 'jmp start' on its own WOULD BE valid)      */

    if (rimout || binout || asmout || incout || octout || useout) {
        if (words) {
            if (rimout) rimout_gen(rimout, rimapp);
            if (binout) binout_gen(binout, binapp);
            if (asmout) asmout_gen(asmout);
            if (incout) incout_gen(incout);
            if (octout) octout_gen(octout);
            if (useout) useout_gen(useout);
        } else {
            fprintf(stderr,
                    "Nothing was loaded, so no output files were created\n");
            exit_status = EXIT_FAILURE;
        }
    } else {
        fprintf(stderr,
                "%successful but no output file options %s were supplied\n",
                    (exit_status == EXIT_SUCCESS) ? "S" : "NOT s",
                    "(-r -b -a -c -o -u)");
    /*  exit_status = EXIT_FAILURE;  */
    }

    /* It's all good (or, at least, we hope so) */

    if (dbgout) {
        fprintf(dbgfile, "Exit status is EXIT_%s\n",
                (exit_status == EXIT_SUCCESS) ? "SUCCESS" : "FAILURE");
        fclose(dbgfile);
    }

    return exit_status;
}
