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

/*
     This program is a macro-processor written specifically to expand the code
     in the .sa assembly files at https://github.com/brouhaha/fp09/tree/master/src
     - it is not robust enough to be used on arbitrary new code.  It was written
     specifically to remove the macros in these files so that the remaining 
     assembly language code could then be converted 1:1 to any current assembler.

     It's a quick hack.  Don't use this as a template for writing macro processors!

     Rather than use the macro bodies defined in the .sa files, it embeds the
     macro expansion in the C code below, and throws away the macro definitions
     when it hits them in the source files.

     Syntax is:   expand file.sa

     and it writes the output to file.asm


     When testing, one of the input files was found to have some corruption (missing
     ENDIFs to some IF statements) and there were two occurances of symbolic literals
     being used, where this code relies on seeing actual literal numbers.  The source
     file for that instance has to be edited as well:

gtoal@linux:~/src/asm-expand$ diff fp09/ins.sa fp09/ins.sa~
512c512
<      MUL10   X,2                    EXPLEN = 2
---
>      MUL10   X,EXPLEN
515c515
<      MUL10  X,8                     ACCLEN = SIGLEN-1 = 9-1 = 8
---
>      MUL10  X,ACCLEN


gtoal@linux:~/src/asm-expand$ diff fp09/util.sa fp09/util.sa~
147,154c147
<       IFCC NE         File appears to be corrupt here... **************************
<       ENDIF
<     ENDIF
<   ENDIF
<   RTS
<
<
<
---
>       IFCC NE**************************


     Graham Toal  2 April 2020.
 */

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

int verbose = TRUE;

FILE *infile, *actual_outfile, *outfile, *nullout;
long start_of_line;

// To keep the installation of this code simple, we'll define the macros in
// the C code rather than via an external file that might get separated.

#define ADCD 0
#define ADRTBL 1
#define BACK1 2
#define BCLRA 3
#define BCLRB 4
#define BSETA 5
#define BSETB 6
#define DECD 7
#define DECS 8
#define DECU 9
#define DECX 10
#define DECY 11
#define DOWN 12
#define ELSE 13
#define ENDIF 14
#define ENDWH 15
#define FPNUM 16
#define IF 17
#define IFCC 18
#define IFTST 19
#define INCD 20
#define INCS 21
#define INCU 22
#define INCX 23
#define INCY 24
#define IOP 25
#define LSHIFT 26
#define MOVA 27
#define MOVB 28
#define MOVD 29
#define MOVS 30
#define MOVU 31
#define MOVX 32
#define MOVY 33
#define MUL10 34
#define POP 35
#define PUSH 36
#define REGTST 37
#define RELCC 38
#define RELOP 39
#define RELTST 40
#define REPEAT 41
#define RIGHT1 42
#define RSHIFT 43
#define SBCD 44
#define SLD 45
#define SRD 46
#define UNTIL 47
#define UP 48
#define WHILE 49
#define XPLUSY 50
#define XSBTRY 51
#define DCALL 52
#define MCALL 53
#define DECBIN 54
#define BINDEC 55

char *macro[] = {
  "ADCD","ADRTBL","BACK1","BCLRA","BCLRB","BSETA","BSETB","DECD",
  "DECS","DECU","DECX","DECY","DOWN","ELSE","ENDIF","ENDWH",
  "FPNUM","IF","IFCC","IFTST","INCD","INCS","INCU","INCX","INCY",
  "IOP","LSHIFT","MOVA","MOVB","MOVD","MOVS","MOVU","MOVX","MOVY",
  "MUL10","POP","PUSH","REGTST","RELCC","RELOP","RELTST",
  "REPEAT","RIGHT1","RSHIFT","SBCD","SLD","SRD","UNTIL","UP",
  "WHILE","XPLUSY","XSBTRY","DCALL", "MCALL", "DECBIN", "BINDEC",
  NULL
};

int argcnt[] = {
//"ADCD","ADRTBL","BACK1","BCLRA","BCLRB","BSETA","BSETB","DECD",       // ADRTBL can take 1 to 5 args.  Others may be similarly optional.
  1,     5,       0,      2,      2,      2,      2,      0,
//"DECS","DECU","DECX","DECY","DOWN","ELSE","ENDIF","ENDWH",
  0,     0,     0,     0,     1,     0,     0,      0,
//"FPNUM","IF","IFCC","IFTST","INCD","INCS","INCU","INCX","INCY",
  13,     3,   1,     3,      0,     0,     0,     0,     0,
//"IOP","LSHIFT","MOVA","MOVB","MOVD","MOVS","MOVU","MOVX","MOVY",
  1,    3,       2,     2,     2,     2,     2,     2,     2,
//"MUL10","POP","PUSH","REGTST","RELCC","RELOP","RELTST",               // not sure why MUL10 body seems to check for 2 args - only uses 1.
  2,      0,    1,     1,       1,      1,      1,
//"REPEAT","RIGHT1","RSHIFT","SBCD","SLD","SRD","UNTIL","UP",
  0,       0,       3,       1,     1,    1,    3,      1,
//"WHILE","XPLUSY","XSBTRY","DCALL", "MCALL", "DECBIN", "BINDEC",
  3,      0,       0,       4,        3,      2,        3,
  -1
};

static int c;  // lookahead character throughout code.  Bit hacky, but it was a 1hr program ...

int fixgetc(FILE *f) {
  int c = fgetc(f);
  if (c == '\r') c = fgetc(f);
  return c;
}

void put_string(char *s) {
  fprintf(outfile, "%s", s);
}

void drain(void) {
  while (c != '\n') { fputc(c, outfile); c = fixgetc(infile); }
  fputc(c, outfile);
}

static char lab[128];
void parse_label(int lookahead) {
  char *s = lab;
  c = lookahead;
  do {
    *s++ = c;
    c = fixgetc(infile);
  } while ((c != '\n') && (!isblank(c)));
  *s = '\0';
}

static char opcode[128];
void parse_opcode(int lookahead) {
  char *s = opcode;
  c = lookahead;
  do {
    *s++ = c;
    c = fixgetc(infile);
  } while ((c != '\n') && (!isblank(c)));
  *s = '\0';
}

int ismacro(char *s, int *argc, int *macidx) {
  int i = 0;
  while (macro[i]) {
    *argc = argcnt[i]; *macidx = i;
    if (strcasecmp(s, macro[i]) == 0) return TRUE;
    i = i + 1;
  }
  *argc = -1;
  return FALSE;
}

#define MAX_NEST 16
#define MAX_OPDS 16
static char operand[MAX_NEST][MAX_OPDS][128];
static int labelstack[MAX_NEST];
static int next_nest = 0;
static int next_lab = 1000;

void parse_operand(int lookahead, int idx) {
  int balance;
  char *s = &operand[next_nest][idx][0];
  balance = 0;
  if (lookahead != ',') *s++ = lookahead;
  if ((lookahead == '(') || (lookahead == '[')) balance = balance + 1;  
  for (;;) {
    c = fixgetc(infile);
    if ((c == '(') || (c == '[')) balance = balance + 1;
    if ((c == ')') || (c == ']')) balance = balance - 1;
    if ((c == ',') && (balance == 0)) break;
    if (isblank(c) || (c == '\n')) break;
    *s++ = c;
  }
  *s = '\0';
  
}

char *invert(char *cc) {
  char *tst[] = { "EQ", "NE", "LT", "LE", "GT", "GE", "CC", "CS", "VC", "VS", NULL}; // see manual for rest HI LO etc
  char *inv[] = { "NE", "EQ", "GE", "LT", "LE", "LT", "CS", "CC", "VS", "VC", NULL};
  int i = 0;
  while (tst[i]) {
    if (strcasecmp(cc, tst[i]) == 0) {
      return inv[i]; // might want to match case later...
    }
    i += 1;
  }
  fprintf(stderr, "Unknown CC: '%s'\n", cc);
  return cc; // or exit???
}

int expected;

void expand(char *opcode, int operands, int op) {
  int i, base_lab;
  char *opd[MAX_OPDS+1];
  if (verbose) {
    fprintf(outfile, "\n* %s", opcode);
    for (i = 1; i <= operands; i++) {
      fprintf(outfile, " [%s]", operand[next_nest][i]);
    }
    fprintf(outfile, "\n");
  }
  strcpy(operand[next_nest][0], opcode);
  switch (op) {

  case IF:
    opd[0] = "IF";
    opd[1] = operand[next_nest][1];
    opd[2] = operand[next_nest][2];
    opd[3] = operand[next_nest][3];
    labelstack[next_nest++] = base_lab = next_lab; next_lab += 1;
    fprintf(outfile, "       CMP%s %s\n", opd[1], opd[3]);
    fprintf(outfile, "       B%s endif%04d\n", invert(opd[2]), base_lab);
    return;

  case IFCC:
    opd[0] = "IFCC";
    opd[1] = operand[next_nest][1];
    opd[2] = operand[next_nest][2];
    opd[3] = operand[next_nest][3];
    labelstack[next_nest++] = base_lab = next_lab; next_lab += 1;
    fprintf(outfile, "      B%s endif%04d\n", invert(opd[1]), base_lab);
    return;

  case IFTST:
    opd[0] = "IF";
    opd[1] = operand[next_nest][1];
    opd[2] = operand[next_nest][2];
    opd[3] = operand[next_nest][3];
    labelstack[next_nest++] = base_lab = next_lab; next_lab += 1;
    fprintf(outfile, "       TST %s\n", opd[1]);
    fprintf(outfile, "       B%s endif%04d\n", invert(opd[2]), base_lab);
    /*
**********************************************************************
*
*	IFTST --
*		THE 'IFTST' MACRO OPERATES LIKE AN 'IF' MACRO EXCEPT
*	THAT IT GENERATES A 'TST' INSTRUCTION INSTEAD OF A 'CMP'.
*	THE SYNTAX IS:
*
*	IFTST	<REGISTER OR ADDRESS EXPRESSION>,<RELATIONAL OP>,0
*
*	THE VALID RELATIONAL OPERATORS FOR USE WITH 'IFTST' ARE: 'EQ',
*	'NE', 'LT', AND 'GE'.
*
     */
    return;

  case ELSE:
    if (strcmp(operand[next_nest-1][0], "IF") == 0) {
      strcpy(operand[next_nest-1][0], "IF;ELSE");
    } else if (strcmp(operand[next_nest-1][0], "IFCC") == 0) {
      strcpy(operand[next_nest-1][0], "IFCC;ELSE");
    } else {
      // coding error
    }
    opd[1] = operand[next_nest-1][1];
    opd[2] = operand[next_nest-1][2];
    opd[3] = operand[next_nest-1][3];
    base_lab = labelstack[next_nest - 1];
    fprintf(outfile, "      B endelse%04d\n", base_lab);
    fprintf(outfile, "endif%04d\n", base_lab);
    return;
    
  case ENDIF:
    base_lab = labelstack[--next_nest];
    opd[1] = operand[next_nest][1];
    opd[2] = operand[next_nest][2];
    opd[3] = operand[next_nest][3];

    if (strcmp(operand[next_nest][0], "IF") == 0) {
      fprintf(outfile, "\nendif%04d\n", base_lab);
    } else if (strcmp(operand[next_nest][0], "IFCC") == 0) {
      fprintf(outfile, "\nendif%04d\n", base_lab);
    } else if (strcmp(operand[next_nest][0], "IFTST") == 0) {
      fprintf(outfile, "\nendif%04d\n", base_lab);
    } else if (strcmp(operand[next_nest][0], "IF;ELSE") == 0) {
      fprintf(outfile, "\nendelse%04d\n", base_lab);
    } else if (strcmp(operand[next_nest][0], "IFCC;ELSE") == 0) {
      fprintf(outfile, "\nendelse%04d\n", base_lab);
    } else {
      // coding error
      fprintf(stderr, "ENDIF: '%s'\n", operand[next_nest][0]);
      exit(1);
    }
    return;
    
  case WHILE:
    opd[1] = operand[next_nest][1];
    opd[2] = operand[next_nest][2];
    opd[3] = operand[next_nest][3];
    labelstack[next_nest++] = base_lab = next_lab; next_lab += 1;
    fprintf(outfile, "\nwhile%04d\n", base_lab);
    fprintf(outfile, "      CMP%s %s\n", opd[1], opd[3]);
    fprintf(outfile, "      B%s endwhile%04d\n", invert(opd[2]), base_lab);
    return;
    
  case ENDWH:
    base_lab = labelstack[--next_nest];
    opd[1] = operand[next_nest][1];
    opd[2] = operand[next_nest][2];
    opd[3] = operand[next_nest][3];
    fprintf(outfile, "      BRA while%04d\n", base_lab);
    fprintf(outfile, "endwhile%04d\n", base_lab);
    return;

  case REPEAT:
    labelstack[next_nest++] = base_lab = next_lab; next_lab += 1;
    fprintf(outfile, "\nuntil%04d\n", base_lab);
    return;

  case UNTIL:
    opd[1] = operand[next_nest][1];
    opd[2] = operand[next_nest][2];
    opd[3] = operand[next_nest][3];
    base_lab = labelstack[--next_nest];
    fprintf(outfile, "      CMP%s %s\n", opd[1], opd[3]);
    fprintf(outfile, "      B%s until%04d\n", invert(opd[2]), base_lab);
    return;

  case RIGHT1:
    fprintf(outfile, "      LSRA\n");
    fprintf(outfile, "      RORB\n");
    fprintf(outfile, "      ROR  3,X\n");
    fprintf(outfile, "      ROR  4,X\n");
    fprintf(outfile, "      ROR  5,X\n");
    fprintf(outfile, "      ROR  6,X\n");
    fprintf(outfile, "      ROR  7,X\n");
    return;

  case RSHIFT:
    opd[1] = operand[next_nest][1]; // offset
    opd[2] = operand[next_nest][2]; // x y u
    opd[3] = operand[next_nest][3]; // length
    labelstack[next_nest] = base_lab = next_lab++;
    {
      int rc, i, iLength = 0;
      rc = sscanf(opd[3], "%d", &iLength);
      if (rc != 1) { fprintf(stderr, "expand: need a literal number in RSHIFT parameter '%s'\n", opd[3]); exit(EXIT_FAILURE); }
      if ((iLength < 1) || (iLength > 10)) {
        fprintf(stderr, "expand: RSHIFT parameter must be between 1 and 10\n");
        exit(EXIT_FAILURE);
      }
      for (i = 0; i < iLength; i++) {
        fprintf(outfile, "      ROR (%s+%s-%d),%s\n", opd[1],opd[3],i,opd[2]);
      }
    }
    /*
*
*
*  THIS MACRO DOES A RIGHT SHIFT ON A MULTI-
* PRECISION OPERAND THAT IS UP TO 10 BYTES
* LONG, WHOSE MOST SIG. BYTE IS POINTED TO
* BY EITHER 0FF,X , OFF,Y , OR OFF,U; WHERE
* "OFF" IS A CONSTANT OFFSET SPECIFIED UPON
* INVOCATION OF THE MACRO. THE CARRY IS SHIFTED
* IN FROM THE RIGHT.
*
*
* TO INVOKE RSHIFT:
*
*     RSHIFT  <"OFF">,< X, Y OR U >,< BYTE LENGTH >
*/
    return;

  case SBCD:
    opd[1] = operand[next_nest][1];
    fprintf(outfile, "      SBCB	1+%s\n", opd[1]);
    fprintf(outfile, "      SBCA	%s\n", opd[1]);
    return;

  case ADCD:
    opd[1] = operand[next_nest][1];
    fprintf(outfile, "      ADCB	1+%s\n", opd[1]);
    fprintf(outfile, "      ADCA	%s\n", opd[1]);
    return;

  case ADRTBL:
    {
      int i;
      for (i = 1; i <= expected; i++) opd[i] = operand[next_nest][i];
      fprintf(outfile, "        FDB ");
      for (i = 1; i <= expected; i++) {
        fprintf(outfile, "%s-ROMSTR", opd[i]);
        if (i < expected) fprintf(outfile, ",");
      }
      fprintf(outfile, "\n");
    }
    return;

  case BACK1:
    fprintf(stderr, "expand: missing macro expansion for BACK1\n");
    return;

  case BCLRA: // looks like 'bit clear' - remove any bits in param from reg
    opd[1] = operand[next_nest][1];
    opd[2] = operand[next_nest][2];
    labelstack[next_nest] = base_lab = next_lab++;
    fprintf(outfile, "      LDA %s\n", opd[2]);
    fprintf(outfile, "      ANDA #(-(%s)-1)!.$FF\n", opd[1]);  // this syntax for complementing a constant is probably not portable
    fprintf(outfile, "      STA %s\n", opd[2]);                // so may need to be changed to whatever assembler ends up being used.
    /*
      LDA \1
      ANDA #(-(\0)-1)!.$FF
      STA \1

     */
    return;
  case BCLRB:
    opd[1] = operand[next_nest][1];
    opd[2] = operand[next_nest][2];
    labelstack[next_nest] = base_lab = next_lab++;
    fprintf(outfile, "      LDB %s\n", opd[2]);
    fprintf(outfile, "      ANDB #(-(%s)-1)!.$FF\n", opd[1]);
    fprintf(outfile, "      STB %s\n", opd[2]);
    /*
      LDB \1
      ANDB  ((-(\0)-1)!.$FF
      STB \1
     */
    return;
  case BSETA:
    opd[1] = operand[next_nest][1];
    opd[2] = operand[next_nest][2];
    labelstack[next_nest] = base_lab = next_lab++;
    fprintf(outfile, "      LDA %s\n", opd[2]);
    fprintf(outfile, "      ORA #(%s)\n", opd[1]);
    fprintf(outfile, "      STA %s\n", opd[2]);
    /*
      LDA \1
      ORA #(\0)
      STA \1
     */
    return;
  case BSETB:
    opd[1] = operand[next_nest][1];
    opd[2] = operand[next_nest][2];
    labelstack[next_nest] = base_lab = next_lab++;
    fprintf(outfile, "      LDB %s\n", opd[2]);
    fprintf(outfile, "      ORB #(%s)\n", opd[1]);
    fprintf(outfile, "      STB %s\n", opd[2]);
    /*
      LDB \1
      ORB #(\0)
      STB \1
     */
    return;
  case DECD:
    opd[1] = operand[next_nest][1];
    opd[2] = operand[next_nest][2];
    labelstack[next_nest] = base_lab = next_lab++;
    fprintf(outfile, "      TSTB\n");
    fprintf(outfile, "      BNE skip%04d\n", base_lab);
    fprintf(outfile, "      DECA\n");
    fprintf(outfile, "skip%04d\n", base_lab);
    fprintf(outfile, "      DECB\n");
    /*
 TSTB
 BNE \.1
 DECA
\.1 DECB
     */
    return;
  case DECS:
    fprintf(outfile, "      LEAS -1,S\n");
    /*
 LEAS -1,S
     */
    return;
  case DECU:
    fprintf(outfile, "      LEAU -1,U\n");
    /*
 LEAU -1,U
     */
    return;
  case DECX:
    fprintf(outfile, "      LEAX -1,X\n");
    /*
 LEAX -1,X
     */
    return;
  case DECY:
    fprintf(outfile, "      LEAY -1,Y\n");
    /*
 LEAY -1,Y
     */
    return;
  case DOWN:
    opd[1] = operand[next_nest][1];
    opd[2] = operand[next_nest][2];
    labelstack[next_nest] = base_lab = next_lab++;
    fprintf(outfile, "      LDB %s\n", opd[1]);
    fprintf(outfile, "      LBSR DWNSUB\n");
    /*
  LDB  #\0
  LBSR DWNSUB
     */
    return;
  case FPNUM:
    {
      int i;
      for (i = 1; i <= expected; i++) opd[i] = operand[next_nest][i];
      fprintf(outfile, "        FCB ");
      for (i = 1; i <= expected; i++) {
        fprintf(outfile, "$%s", opd[i]);
        if (i < expected) fprintf(outfile, ",");
      }
      fprintf(outfile, "\n");
    }
    /*
  FCB  $\0,$\1,$\2,$\3,$\4,$\5,$\6,$\7,$\8,$\9,$\A,$\B,$\C
     */
    return;

  case INCD:
    labelstack[next_nest] = base_lab = next_lab++;
    fprintf(outfile, "      INCB\n");
    fprintf(outfile, "      BNE skip%04d\n", base_lab);
    fprintf(outfile, "      INCA\n");
    fprintf(outfile, "skip%04d\n", base_lab);
    /*
 INCB
 BNE *+3
 INCA
     */
    return;
  case INCS:
    fprintf(outfile, "      LEAS 1,S\n");
    /*
 LEAS 1,S
     */
    return;
  case INCU:
    fprintf(outfile, "      LEAU 1,U\n");
    /*
 LEAU 1,U
     */
    return;
  case INCX:
    fprintf(outfile, "      LEAX 1,X\n");
    /*
 LEAX 1,X
     */
    return;
  case INCY:
    fprintf(outfile, "      LEAY 1,Y\n");
    /*
 LEAY 1,Y
     */
    return;
  case IOP:
    opd[1] = operand[next_nest][1];
    labelstack[next_nest] = base_lab = next_lab++;
    fprintf(outfile, "      LDA #(%s)\n", opd[1]);
    fprintf(outfile, "      LBSR  IOPSUB           SET IOP CODE,IOP BIT & RETURN A NAN\n");
    /*
* CHECK FOR ILLEGAL CASES
 IFLT (\0)-1
 FAIL *** IOP N; N > 0 ***
 ENDC
 IFGT (\0)-BIGIOP
 FAIL *** IOP N; N TOO BIG ***
 ENDC
* NOT ILLEGAL
 LDA #(\0)
 LBSR  IOPSUB		SET IOP CODE,IOP BIT & RETURN A NAN
     */
    return;
  case LSHIFT:
    opd[1] = operand[next_nest][1]; // offset
    opd[2] = operand[next_nest][2]; // x y u
    opd[3] = operand[next_nest][3]; // length
    labelstack[next_nest] = base_lab = next_lab++;
    {
      int rc, i, iLength;
      rc = sscanf(opd[3], "%d", &iLength);
      if (rc != 1) { fprintf(stderr, "expand: need a literal number in LSHIFT parameter '%s'\n", opd[3]); exit(EXIT_FAILURE); }
      if ((iLength < 1) || (iLength > 10)) {
        fprintf(stderr, "expand: LSHIFT parameter must be between 1 and 10\n");
        exit(EXIT_FAILURE);
      }
      for (i = 0; i < iLength; i++) {
        fprintf(outfile, "      ROL (%s+%s-%d),%s\n", opd[1],opd[3],i,opd[2]);
      }
    }
    /*
*
*
*  THIS MACRO DOES A LEFT SHIFT ON A MULTI-
* PRECISION OPERAND THAT IS UP TO 10 BYTES
* LONG, WHOSE MOST SIG. BYTE IS POINTED TO
* BY EITHER 0FF,X , OFF,Y , OR OFF,U; WHERE
* "OFF" IS A CONSTANT OFFSET SPECIFIED UPON
* INVOCATION OF THE MACRO. THE CARRY IS SHIFTED
* IN FROM THE LEFT.
*
* TO INVOKE LSHIFT:
*
*     LSHIFT  <"0FF">,< X,Y OR U >,< BYTE LENGTH >
*/
    return;

  case MOVA:
    opd[1] = operand[next_nest][1];
    opd[2] = operand[next_nest][2];
    labelstack[next_nest] = base_lab = next_lab++;
    fprintf(outfile, "      LDA %s\n", opd[1]);
    fprintf(outfile, "      STA %s\n", opd[2]);
    /*
     LDA \0
     STA \1
     */
    return;
  case MOVB:
    opd[1] = operand[next_nest][1];
    opd[2] = operand[next_nest][2];
    labelstack[next_nest] = base_lab = next_lab++;
    fprintf(outfile, "      LDB %s\n", opd[1]);
    fprintf(outfile, "      STB %s\n", opd[2]);
    /*
     LDB \0
     STB \1
     */
    return;
  case MOVD:
    opd[1] = operand[next_nest][1];
    opd[2] = operand[next_nest][2];
    labelstack[next_nest] = base_lab = next_lab++;
    fprintf(outfile, "      LDD %s\n", opd[1]);
    fprintf(outfile, "      STD %s\n", opd[2]);
    /*
     LDD \0
     STD \1
     */
    return;
  case MOVS:
    opd[1] = operand[next_nest][1];
    opd[2] = operand[next_nest][2];
    labelstack[next_nest] = base_lab = next_lab++;
    fprintf(outfile, "      LDS %s\n", opd[1]);
    fprintf(outfile, "      STS %s\n", opd[2]);
    /*
     LDS \0
     STS \1
     */
    return;
  case MOVU:
    opd[1] = operand[next_nest][1];
    opd[2] = operand[next_nest][2];
    labelstack[next_nest] = base_lab = next_lab++;
    fprintf(outfile, "      LDU %s\n", opd[1]);
    fprintf(outfile, "      STU %s\n", opd[2]);
    /*
     LDU \0
     STU \1
     */
    return;
  case MOVX:
    opd[1] = operand[next_nest][1];
    opd[2] = operand[next_nest][2];
    labelstack[next_nest] = base_lab = next_lab++;
    fprintf(outfile, "      LDX %s\n", opd[1]);
    fprintf(outfile, "      STX %s\n", opd[2]);
    /*
     LDX \0
     STX \1
     */
    return;
  case MOVY:
    opd[1] = operand[next_nest][1];
    opd[2] = operand[next_nest][2];
    labelstack[next_nest] = base_lab = next_lab++;
    fprintf(outfile, "      LDY %s\n", opd[1]);
    fprintf(outfile, "      STY %s\n", opd[2]);
    /*
     LDY \0
     STY \1
     */
    return;
  case MUL10:
    opd[1] = operand[next_nest][1];  // "X"
    opd[2] = operand[next_nest][2];  // byte length of operand
    labelstack[next_nest] = base_lab = next_lab; next_lab += 3;
    /*
****************************************************
*
*   MUL10
*
*     MUL10 TAKES A MULTI- PRECISION BINARY INTEGER
* AND MULTIPLIES IT BY 10. THIS OPERATION IS USEFULL
* FOR PERFORMING DECIMAL TO BINARY CONVERSIONS. UPON
* INVOCATION REG. X MUST POINT TO THE MSBYTE OF THE
* MULTI-BYTE ARGUMENT.
*
* ON ENTRY: X - POINTS TO THE MSBYTE OF THE INPUT
*	    OPERAND.
*
* ON EXIT: THE INPUT ARGUMENT IS MULTIPLIED BY 10
*
*	   X IS UNCHANGED; A & B DESTROYED
*
*   TO INVOKE MUL10:
*
*	    MUL10      X,< BYTE LENGTH OF OPERAND >
*
* CHECK FOR PROPER NO. OF ARGUMENTS
*
 IFNE  NARG-2
   FAIL  ** TOO MANY OR TOO FEW INPUT ARGUMENTS **
   EXIT
 ENDC
*
* CREATE A TEMPORARY A TEMPORARY ACCUMULATOR ON THE STACK
*
*/
    fprintf(outfile, "      LEAS  -%s,S\n", opd[2]);
    fprintf(outfile, "* MULTIPLY INPUT BY 2 I.E. LEFT SHIFT ONCE\n");
    fprintf(outfile, "      ANDCC #NC		       CLEAR CARRY\n");
    //fprintf(outfile, "      LSHIFT  0,X,(%s)	       LEFT SHIFT\n", opd[2]);
    {
      int rc, i, iLength;
      rc = sscanf(opd[2], "%d", &iLength);
      if (rc != 1) { fprintf(stderr, "expand: need a literal number in MUL10:LSHIFT parameter '%s'\n", opd[2]); exit(EXIT_FAILURE); }
      for (i = 0; i < iLength; i++) {
        fprintf(outfile, "      ROL (%s+(%s)-%d),%s\n", "0",opd[2],i,"X"); // WARNING: parameter in .sa file is "EXPLEN" or "ACCLEN"
      }                                                                    // rather than a literal number.  I have changed my copy
    }                                                                      // to 2 and 8 so that these expansions work.
    fprintf(outfile, "* COPY ARG*2 TO A TEMPORARY ACCUMULATOR\n");
    fprintf(outfile, "\n");
    fprintf(outfile, "      LDB  #(%s)		       ARGUMENT SIZE\n", opd[2]);

    // WHILE	B,GT,#0
    fprintf(outfile, "\nwhile%04d\n", base_lab);
    fprintf(outfile, "      CMP%s %s\n", "B", "#0");
    fprintf(outfile, "      B%s endwhile%04d\n", invert("GT"), base_lab);

    fprintf(outfile, "        DECB\n");
    fprintf(outfile, "        LDA	B,X		       SOURCE\n");
    fprintf(outfile, "        STA	B,S		       DESTINATION\n");

    // ENDWH
    fprintf(outfile, "      BRA while%04d\n", base_lab);
    fprintf(outfile, "endwhile%04d\n", base_lab);

    fprintf(outfile, "* LEFT SHIFT ARGUMENT TWICE MORE TO YIELD 8*\n");
    fprintf(outfile, "* INPUT ARGUMENT.\n");
    fprintf(outfile, "      LDB  #2\n");

    // WHILE	B,GT,#0
    fprintf(outfile, "\nwhile%04d\n", base_lab+1);
    fprintf(outfile, "      CMP%s %s\n", "B", "#0");
    fprintf(outfile, "      B%s endwhile%04d\n", invert("GT"), base_lab+1);

    fprintf(outfile, "        ANDCC  #NC		       CLEAR CARRY\n");
    //fprintf(outfile, "        LSHIFT  0,X,(%s)	       LEFT SHIFT\n", opd[2]);
    {
      int rc, i, iLength;
      rc = sscanf(opd[2], "%d", &iLength);
      if (rc != 1) { fprintf(stderr, "expand: need a literal number in MUL10:LSHIFT parameter '%s'\n", opd[2]); exit(EXIT_FAILURE); }
      for (i = 0; i < iLength; i++) {
        fprintf(outfile, "      ROL (%s+(%s)-%d),%s\n", "0",opd[2],i,"X");
      }
    }

    fprintf(outfile, "        DECB 		       DECREMENT COUNTER\n");

    // ENDWH
    fprintf(outfile, "      BRA while%04d\n", base_lab+1);
    fprintf(outfile, "endwhile%04d\n", base_lab+1);
   
    fprintf(outfile, "* NOW ADD 8*INPUT TO 2*INPUT TO YIELD 10*INPUT\n");
    fprintf(outfile, "      LDB  #(%s)		       ARGUMENT LENGTH\n", opd[2]);
    fprintf(outfile, "      DECB			       CONVERT LENGTH TO OFFSET\n");
    fprintf(outfile, "      CLRA\n");

    // WHILE	B,GE,#0
    fprintf(outfile, "\nwhile%04d\n", base_lab+2);
    fprintf(outfile, "      CMP%s %s\n", "B", "#0");
    fprintf(outfile, "      B%s endwhile%04d\n", invert("GE"), base_lab+2);

    fprintf(outfile, "        RORA 		       RESTORE CARRY\n");
    fprintf(outfile, "        LDA	B,X		       GET ARG1\n");
    fprintf(outfile, "        ADCA  B,S		       ADD IN ARG2\n");
    fprintf(outfile, "        STA	B,X		       SAVE RESULT\n");
    fprintf(outfile, "        ROLA 		       SAVE CARRY IN A\n");
    fprintf(outfile, "        DECB 		       DECREMENT INDEX\n");

    // ENDWH
    fprintf(outfile, "      BRA while%04d\n", base_lab+2);
    fprintf(outfile, "endwhile%04d\n", base_lab+2);
    
    fprintf(outfile, "     LEAS  %s,S		       CLEAN UP STACK\n", opd[2]);
    return;

  case POP:
    fprintf(stderr, "expand: missing macro expansion for POP\n");
    return;

  case PUSH:
    fprintf(stderr, "expand: missing macro expansion for PUSH\n");
    return;

  case REGTST:
    fprintf(stderr, "expand: missing macro expansion for REGTST\n");
    return;

  case RELCC:
    fprintf(stderr, "expand: missing macro expansion for RELCC\n");
    return;

  case RELOP:
    fprintf(stderr, "expand: missing macro expansion for RELOP\n");
    return;

  case RELTST:
    fprintf(stderr, "expand: missing macro expansion for RELTST\n");
    return;

  case SLD:
    opd[1] = operand[next_nest][1];
    labelstack[next_nest] = base_lab = next_lab++;
    {
      int rc, i, iN;
      rc = sscanf(opd[1], "%d", &iN);
      if (rc != 1) { fprintf(stderr, "expand: need a literal number in SLD parameter '%s'\n", opd[1]); exit(EXIT_FAILURE); }
      if ((iN < 0) || (iN > 8)) {
        fprintf(stderr, "expand: SLD parameter must be between 0 and 8\n");
        exit(EXIT_FAILURE);
      }
      for (i = 0; i < iN; i++) {
        fprintf(outfile, "      LSLB\n");
        fprintf(outfile, "      ROLA\n");
      }
    }
    return;

  case SRD:
    opd[1] = operand[next_nest][1];
    labelstack[next_nest] = base_lab = next_lab++;
    {
      int rc, i, iN;
      rc = sscanf(opd[1], "%d", &iN);
      if (rc != 1) { fprintf(stderr, "expand: need a literal number in SRD parameter '%s'\n", opd[1]); exit(EXIT_FAILURE); }
      if ((iN < 0) || (iN > 8)) {
        fprintf(stderr, "expand: SRD parameter must be between 0 and 8\n");
        exit(EXIT_FAILURE);
      }
      for (i = 0; i < iN; i++) {
        fprintf(outfile, "      LSRA\n");
        fprintf(outfile, "      RORB\n");
      }
    }
    return;

  case UP:
    opd[1] = operand[next_nest][1];
    labelstack[next_nest] = base_lab = next_lab++;
    fprintf(outfile, "      LDB #%s\n", opd[1]);
    fprintf(outfile, "      LBRA DO_UP\n");
    return;

  case XPLUSY:
    fprintf(outfile, "      LDD  7,X\n");
    fprintf(outfile, "      ADDD 7,Y\n");
    fprintf(outfile, "      STD  7,X\n");
    fprintf(outfile, "      LDD  5,X\n");
    fprintf(outfile, "      ADCD (5,Y)\n");
    fprintf(outfile, "      STD  5,X\n");
    fprintf(outfile, "      LDD  3,X\n");
    fprintf(outfile, "      ADCD (3,Y)\n");
    fprintf(outfile, "      STD  3,X\n");
    fprintf(outfile, "      LDD  1,X\n");
    fprintf(outfile, "      ADCD (1,Y)\n");
    fprintf(outfile, "      STD  1,X\n");
    fprintf(outfile, "      LDB  0,X\n");
    fprintf(outfile, "      ADCB 0,Y\n");
    fprintf(outfile, "      STB  0,X\n");
    return;

  case XSBTRY:
    fprintf(outfile, "      LDD  7,X\n");
    fprintf(outfile, "      SUBD 7,Y\n");
    fprintf(outfile, "      STD  7,X\n");
    fprintf(outfile, "      LDD  5,X\n");
    fprintf(outfile, "      SBCD (5,Y)\n");
    fprintf(outfile, "      STD  5,X\n");
    fprintf(outfile, "      LDD  3,X\n");
    fprintf(outfile, "      SBCD (3,Y)\n");
    fprintf(outfile, "      STD  3,X\n");
    fprintf(outfile, "      LDD  1,X\n");
    fprintf(outfile, "      SBCD (1,Y)\n");
    fprintf(outfile, "      STD  1,X\n");
    fprintf(outfile, "      LDB  0,X\n");
    fprintf(outfile, "      SBCB 0,Y\n");
    fprintf(outfile, "      STB  0,X\n");
    return;

    //
    // HERE ARE THE CALLING SEQUENCE MACROS for quad test
    //
    
  case MCALL:
    opd[1] = operand[next_nest][1]; // 
    opd[2] = operand[next_nest][2]; // 
    opd[3] = operand[next_nest][3]; // 
    labelstack[next_nest] = base_lab = next_lab++;
    //
    //  MCALL SETS UP A MONADIC REGISTER CALL.
    //
    //  USAGE: MCALL <INPUT OPERAND>,<OPERATION>,<RESULT>
    //
    fprintf(outfile, "        LEAY    %s,PCR          POINTER TO THE INPUT ARGUMENT\n", opd[1]);
    fprintf(outfile, "        LEAX    FPCB,PCR        POINTER TO THE FLOATING POINT CONTROL BLOCK\n");
    fprintf(outfile, "        TFR     X,D\n");
    fprintf(outfile, "        LEAX    %s,PCR          POINTER TO THE RESULT\n", opd[3]);
    fprintf(outfile, "        LBSR    FPREG           CALL TO THE MC6839\n");
    fprintf(outfile, "        FCB     %s              OPCODE\n", opd[2]);
    return;

  case DCALL:
    opd[1] = operand[next_nest][1]; // 
    opd[2] = operand[next_nest][2]; // 
    opd[3] = operand[next_nest][3]; // 
    opd[4] = operand[next_nest][4]; // 
    labelstack[next_nest] = base_lab = next_lab++;
    //
    //  DCALL SETS UP A DYADIC REGISTER CALL
    //
    //  USAGE: DCALL <ARGUMENT #1>,<OPERATION>,<ARGUMENT #2>,<RESULT>
    //
    fprintf(outfile, "        LEAU    %s,PCR          POINTER TO ARGUMENT #1\n", opd[1]);
    fprintf(outfile, "        LEAY    %s,PCR          POINTER TO ARGUMENT #1\n", opd[3]);
    fprintf(outfile, "        LEAX    FPCB,PCR        POINTER TO THE FLOATING POINT CONTROL BLOCK\n");
    fprintf(outfile, "        TFR     X,D\n");
    fprintf(outfile, "        LEAX    %s,PCR          POINTER TO THE RESULT\n", opd[4]);
    fprintf(outfile, "        LBSR    FPREG           CALL TO THE MC6839\n");
    fprintf(outfile, "        FCB     %s              OPCODE\n", opd[2]);
    return;

  case DECBIN:
    opd[1] = operand[next_nest][1]; // 
    opd[2] = operand[next_nest][2]; // 
    //    opd[3] = operand[next_nest][3]; // 
    labelstack[next_nest] = base_lab = next_lab++;
    //
    // DECBIN SETS UP A REGISTER CALL TO THE DECIMAL TO BINARY CONVERSION FUNCTION.
    //
    // USAGE: DECBIN  <BCD STRING>,<BINARY RESULT>
    //
    fprintf(outfile, "        LEAU    %s,PCR          POINTER TO THE BCD INPUT STRING\n", opd[1]);
    fprintf(outfile, "        LEAX    FPCB,PCR        POINTER TO THE FLOATING POINT CONTROL BLOCK\n");
    fprintf(outfile, "        TFR     X,D\n");
    fprintf(outfile, "        LEAX    %s,PCR          POINTER TO THE RESULT\n", opd[2]);
    fprintf(outfile, "        LBSR    FPREG           CALL TO THE MC6839\n");
    fprintf(outfile, "        FCB     DCBN            OPCODE\n");
    return;

  case BINDEC:
    opd[1] = operand[next_nest][1]; // 
    opd[2] = operand[next_nest][2]; // 
    opd[3] = operand[next_nest][3]; // 
    labelstack[next_nest] = base_lab = next_lab++;
    //
    // BINDEC SETS UP A REGISTER CALL TO THE BINARY TO DECIMAL CONVERSION FUNCTION.
    //
    // USAGE: BINDEC <BINARY INPUT>,<BCD RESULT>,<# OF SIGNIFICANT DIGITS RESULT>
    //
    fprintf(outfile, "        LDU     %s              # OF SIGNIFICANT DIGITS IN THE RESULT\n", opd[3]);
    fprintf(outfile, "        LEAY    %s,PCR          POINTER TO THE BINARY INPUT\n", opd[1]);
    fprintf(outfile, "        LEAX    FPCB,PCR        POINTER TO THE FLOATING POINT CONTROL BLOCK\n");
    fprintf(outfile, "        TFR     X,D\n");
    fprintf(outfile, "        LEAX    %s,PCR          POINTER TO THE BCD RESULT\n", opd[2]);
    fprintf(outfile, "        LBSR    FPREG           CALL TO THE MC6839\n");
    fprintf(outfile, "        FCB     BNDC            OPCODE\n");
    return;

  default:
    // coding error
    return;
  } // end switch
}

int handle_line(int allow_expand) {
  expected = 0;
  while (isblank(c) && (c != '\n')) { fputc(c, outfile); c = fixgetc(infile); }
    if (c != '\n') {
      int MACNAME, i;
      if (c == '*') {
        drain();
        return TRUE;
      }
      parse_opcode(c);
      while (isblank(c) && (c != '\n')) c = fixgetc(infile);
      if (allow_expand && ismacro(opcode, &expected, &MACNAME)) {
        i = 0; // no operands
        if (c != '\n') {
          for (i = 1; i <= expected; i++) {                       // initially no spaces in parameters, later allow spaces after ','
            parse_operand(c, i); // $1 $2 etc in expansion.  We'll skip 0 just to make coding simpler.
            if ((c != ',') && (i < expected)) break; // expected is a maximum, there may be fewer given...
          }
          if (i < expected) expected = i; // in case it was short
        }
        expand(opcode, expected, MACNAME);
        if (c != '\n') {
          fprintf(outfile, "\n*                                   ");
          drain();
        } else fputc(c, outfile);
      } else if (strcasecmp(opcode, "ENDM") == 0) {
        return FALSE;
      } else if (strncmp(opcode, "MACR", 4) == 0) {
        fflush(outfile);
        fseek(outfile, start_of_line, SEEK_SET);
        fflush(outfile);
        fprintf(outfile, "* Macro body %s removed.\n", lab);
        /*
            We want to completely skip the definition of the macro body, since
            we're defining the macros interally.
            Unfortunately the name of the macro has already been processed as
            a label, preceding the MACR keyword.  So to get rid of that from
            the output file, we do a rather dirty hack, and rewind the output
            stream so that we can overwrite it.  This is why I had to change the
            program to use files in the command line rather than writing to
            stdout, which can't be rewound as it is a stream, not a file.
         */
        outfile = nullout;
        for (;;) {
          c = fixgetc(infile); // always at start of line
          if ((c == EOF) || (c == 26)) break;
          if (c == '\n') {
            fputc(c, outfile);
            continue;
          } else if (!isblank(c)) { // label or comment
            if (c == '*') { // comment
              drain();
              continue;
            } else {
              parse_label(c); put_string(lab);
              if (c == '\n') continue;
            }
          }
          if (!handle_line(FALSE)) break; // exit on ENDM
        }
        outfile = actual_outfile;
      } else {
        fputc(' ', outfile); 
        put_string(opcode);
        fputc(' ', outfile);
        drain();
      }
    } else fputc(c, outfile);
    return TRUE;
}

int main(int argc, char **argv) {
  static char *outfilename;

  // for reasons explained elsewhere, I had to make the input file a parameter rather than
  // use redirection for the inputs and outputs.  If the input file ends in .sa, the
  // extension will be stripped.  ".asm" as always added.

  if (argc != 2) {
    fprintf(stderr, "syntax: expand filename.sa\n");
    exit(EXIT_FAILURE);
  }
  infile = fopen(argv[1], "r");
  if (infile == NULL) {
    fprintf(stderr, "expand: cannot open input %s - %s\n", argv[1], strerror(errno));
    exit(errno);
  }
  outfilename = malloc(strlen(argv[1])+4);
  strcpy(outfilename, argv[1]);
  if (strlen(outfilename) >= 3) {
    if (strcasecmp(outfilename+strlen(outfilename)-3, ".sa") == 0) outfilename[strlen(outfilename)-3] = '\0';
  }
  strcat(outfilename, ".asm");
  actual_outfile = outfile = fopen(outfilename, "w");
  if (outfile == NULL) {
    fprintf(stderr, "expand: cannot open output %s - %s\n", argv[1], strerror(errno));
    exit(errno);
  }
  nullout = fopen("/dev/null", "w"); if (nullout == NULL) nullout = fopen("null.out", "w");
  fprintf(stderr, "Writing output to %s\n", outfilename);
  for (;;) {
    fflush(outfile);
    start_of_line = ftell(outfile);
    c = fixgetc(infile); // always at start of line
    if ((c == EOF) || (c == 26)) break;
    if (c == '\n') {
      fputc(c, outfile);
      continue;
    } else if (!isblank(c)) { // label or comment
      if (c == '*') { // comment
        drain();
        continue;
      } else {
        parse_label(c); put_string(lab);
        if (c == '\n') continue;
      }
    }
    handle_line(TRUE);
  }
  fclose(nullout);
  fflush(stdout);
  exit(EXIT_SUCCESS);
}