https://github.com/virtualagc/virtualagc
Revision 078c79d8734a9ed2860303a7c1662004284fe853 authored by Ron Burkey on 07 August 2022, 15:04:04 UTC, committed by Ron Burkey on 07 August 2022, 15:04:04 UTC
assembly listings from yaASM and yaLEMAP. Added some debugging messages
to 'make install'.  Tweaked debugging messages that VirtualAGC embeds in
'simulate'.  Verified buildability in Mint 21, 20, 19, 17, and verified
buildability using clang in Mint 17.
1 parent 6bb1acc
Raw File
Tip revision: 078c79d8734a9ed2860303a7c1662004284fe853 authored by Ron Burkey on 07 August 2022, 15:04:04 UTC
Fixed a potential string-overflow bug in yaASM. Removed timestamps from
Tip revision: 078c79d
yaASM.c
/*
 * Copyright 2011,2012,2019,2020,2022 Ronald S. Burkey <info@sandroid.org>
 *
 * This file is part of yaAGC.
 *
 * yaAGC is free software; you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation; either version 2 of the License, or
 * (at your option) any later version.
 * yaAGC is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with yaAGC; if not, write to the Free Software
 * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
 *
 * Filename:    yaASM.c
 * Purpose:     Cross-assembler for Gemini OBC.  Was at one time intended
 *              also for the Apollo LVDC, but that is no longer the case.
 *              (For LVDC, refer to yaASM.py instead.)
 * Compiler:    GNU gcc.
 * Reference:   http://www.ibibio.org/apollo
 * Mods:        2010-01-30 RSB  Began adapting from yaLEMAP.c.
 *              2011-12-08 RSB  I've completely discarded the previous
 *                              uncompleted work, and have started
 *                              from scratch based on input from
 *                              original OBC developers
 *                              and on the fact that I had clearly
 *                              bit off more than I could chew by
 *                              trying to shoe-horn this into the
 *                              framework of yaLEMAP ... yes, I
 *                              lose some capabilities by doing so,
 *                              but better to have an assembler with
 *                              reduced capabilities than never to
 *                              have one at all if I can't bring it
 *                              to fruition!
 *              2011-12-16 RSB  I think it's fully functional right
 *                              now, with at least the minimal set of
 *                              new features that I think needed to be
 *                              added to the original ... though not
 *                              necessarily completely debugged.
 *              2011-12-20 RSB  Corrected the SPQ instruction, which
 *                              for some reason I thought stored the
 *                              result in the accumulator rather than
 *                              in memory.
 *              2011-12-23 RSB  Implemented program modules for OBC ...
 *                              turns out to be implemented exactly
 *                              the same way as memory modules for LVDC!
 *                              Also changed the binary output slightly
 *                              in order to incorporate a starting
 *                              HOP constant.
 *              2011-12-27 RSB  Was using wrong sector as residual.
 *              2012-01-07 RSB  Added HOP extensions for HOP, CLA,
 *                              and STO instructions.
 *              2012-01-08 RSB  Fixed to not abort instantly on
 *                              error detection.
 *              2019-09-08 RSB  Backed out anything related to LVDC
 *                              support.  Use yaASM.py instead for LVDC.
 *              2020-12-06 RSB  Changed character stuffed at end of input
 *                              line buffer to avoid a clang warning for
 *                              signed vs unsigned characters.
 *              2022-08-07 RSB  Removed the assembler's timestamp from
 *                              the assembly listing, in favor of a
 *                              yaASM revision code (1.0).  Also,
 *                              the latest compiler version now detects
 *                              a potential string overflow, which I've
 *                              worked around.
 */

#include <stdlib.h>
#include <stdio.h>
#include <string.h>
#include <ctype.h>
#include <math.h>
#include <stdint.h>
#include <time.h>
#include "./yaASM.h"

#define REVISION "1.0"

// Command-line options.
static int HalfWordMode = 0; // Can change during assembly.
static Address_t InstructionPointer, StartingInstructionPointer =
  { 0, 0, 2, 0 }, ObcEntry =
  { 0, 0, 2, 0 };
static Address_t DataPointer, StartingDataPointer =
  { 0, 0, 0, 0 };

enum PassType_t
{
  PT_SYMBOLS, PT_CODE
};
static int
Pass(enum PassType_t PassType);

// Line- and field-buffers.
#define MAX_FIELDS 4
static Line_t InputLine, Comment, Fields[MAX_FIELDS];
static char *FieldStarts[MAX_FIELDS];
static int NumFields;

// Symbol table.
static SymbolList_t Symbols;
static int NumSymbols = 0;

// Variables for tracking files and lines.
static int ErrorCount = 0;
static char *Input = NULL;
static FILE *fin = NULL;
static int LineTotal = 0, LineInFile = 0;
typedef struct
{
  int LineInFile;
  FILE *fin;
  Line_t Input;
} File_t;
#define MAXFILEDEPTH 16
static File_t Files[MAXFILEDEPTH];
static int CurrentFileDepth = 0;

// Buffer for the binary.  This is initially filled
// with the illegal value 0xFFFF, and we use that later
// to detect uninitialized memory or else memory that
// has been overwritten (at assembly time).
static BinaryImage_t Binary;

// List of opcodes and pseudo-ops.
static const char *Operators[] =
  { "HOP", "DIV", "PRO", "RSU", "ADD", "SUB", "CLA", "AND", "MPY", "TRA",
      "SHF", "TMI", "STO", "SPQ", "CLD", "TNZ", "NOP", "SHR", "SHL", "DEC",
      "OCT", "SYN", "EQU", "HOPC" };
enum OperandType_t
{
  OT_ADDRESS, OT_YX, OT_CYX, OT_NONE, OT_SHR, OT_SHL, OT_NOP
};
enum AddressType_t
{
  AT_DATA, AT_CODE
};
enum Operator_t
{
  OP_START = 0,
  OP_HOP = OP_START,
  OP_DIV,
  OP_PRO,
  OP_RSU,
  OP_ADD,
  OP_SUB,
  OP_CLA,
  OP_AND,
  OP_MPY,
  OP_TRA,
  OP_SHF,
  OP_TMI,
  OP_STO,
  OP_SPQ,
  OP_CLD,
  OP_TNZ,
  OP_OFFICIAL,
  OP_NOP = OP_OFFICIAL,
  OP_SHR,
  OP_SHL,
  OP_OPCODES,
  OP_DEC = OP_OPCODES,
  OP_OCT,
  OP_ALLOCS,
  OP_SYN = OP_ALLOCS,
  OP_EQU,
  OP_HOPC,
  OP_COUNT
};
typedef struct
{
  int NumericalOpCode;
  enum OperandType_t OperandType;
  enum AddressType_t AddressType; // Valid only for OperandType==OT_ADDRESS.
} ParseType_t;
static const ParseType_t ParseTypes[OP_OPCODES] =
  {
    { 00, OT_ADDRESS }, // HOP
        { 01, OT_ADDRESS, AT_DATA }, // DIV
        { 02, OT_CYX }, // PRO
        { 03, OT_ADDRESS, AT_DATA }, // RSU
        { 04, OT_ADDRESS, AT_DATA }, // ADD
        { 05, OT_ADDRESS, AT_DATA }, // SUB
        { 06, OT_ADDRESS, AT_DATA }, // CLA
        { 07, OT_ADDRESS, AT_DATA }, // AND
        { 010, OT_ADDRESS, AT_DATA }, // MPY
        { 011, OT_ADDRESS, AT_CODE }, // TRA
        { 012, OT_YX }, // SHF
        { 013, OT_ADDRESS, AT_CODE }, // TMI
        { 014, OT_ADDRESS, AT_DATA }, //STO
        { 015, OT_ADDRESS, AT_DATA }, // SPQ
        { 016, OT_YX }, // CLD
        { 017, OT_ADDRESS, AT_CODE }, // TNZ
        { 011, OT_NOP }, // NOP
        { 012, OT_SHR }, // SHR
        { 012, OT_SHL } };

/////////////////////////////////////////////////////////////////////////
// Various helper functions.

// Write the value of a symbol to binary.
static int
WriteBinary(enum SymbolType_t Type, Address_t *Address, uint32_t Value)
{
  int i, Count = 0, RetVal = 0;
  uint16_t *Destination;

  if (Type == ST_CODE)
    Count = 1;
  else if (Type == ST_VARIABLE || Type == ST_CONSTANT)
    Count = (Address->HalfWordMode ? 1 : 2); // Detect half-word mode.
  for (i = 0; i < Count; i++)
    {
      Destination = &Binary[Address->Module][Address->Page][Address->Syllable
          + i][Address->Word];
      if (*Destination != ILLEGAL_VALUE)
        {
          RetVal++;
          fprintf(stderr, "Trying to overwrite binary at address ");
          fprintf(stderr, "%o-", Address->Module);
          fprintf(stderr, "%02o-%o-%03o\n", Address->Page, Address->Syllable,
              Address->Word);
        }
      else
        *Destination = 0x1FFF & (Value >> (13 * i));
    }
  return (RetVal);
}

// Compares two Symbol_t structures on the basis of symbol
// name, for use with qsort() or bsearch().
static int
CompareSymbols(const void *s1, const void *s2)
{
#define Sym1 ((Symbol_t *) s1)
#define Sym2 ((Symbol_t *) s2)
  return (strcmp(Sym1->Name, Sym2->Name));
}

// Compares two Symbol_t structures based on memory address,
// for use with qsort() or bsearch().
static int
CompareAddresses(const void *s1, const void *s2)
{
#define Sym1 ((Symbol_t *) s1)
#define Sym2 ((Symbol_t *) s2)
  int i;
  i = Sym1->Address.Module - Sym2->Address.Module;
  if (i)
    return (i);
  i = Sym1->Address.Page - Sym2->Address.Page;
  if (i)
    return (i);
  i = Sym1->Address.Syllable - Sym2->Address.Syllable;
  if (i)
    return (i);
  i = Sym1->Address.Word - Sym2->Address.Word;
  if (i)
    return (i);
  i = Sym1->RefType - Sym2->RefType;
  if (i)
    return (i);
  return (strcmp(Sym1->Name, Sym2->Name));
}

// This function finds all of the fields in the input line
// (up to a maximum of MAX_FIELDS), and stores them in
// the Fields[] array.  It also stores (in the FieldStarts[])
// array pointers to the starts of the fields within the
// input line.  The practical difference between Fields[]
// and FieldStarts[] is that the fields are nul-terminated
// in the former and continue to the end of the line in the
// latter.
static void
ParseToFields(void)
{
  char *s, *ss, c;
  int i;
  for (i = 0; i < MAX_FIELDS; i++)
    {
      Fields[i][0] = 0;
      FieldStarts[i] = NULL;
    }
  for (i = 0, s = InputLine; i < MAX_FIELDS; i++, s = ss)
    {
      for (; isspace (*s); s++)
        ; // Move past white-space.
      if (!*s) // End of the line?
        break;
      FieldStarts[i] = s;
      for (ss = s; *ss && !isspace (*ss); ss++)
        ; // Move to end of field.
      c = *ss;
      *ss = 0;
      strcpy(Fields[i], s);
      *ss = c;
    }
  NumFields = i;
}

// Add a SYN/EQU/HOPC back-reference to the symbol listing.
static void
PrintRef(Symbol_t *Symbol)
{
  if (Symbol->RefType == ST_NONE)
    ;
  else if (Symbol->RefType == ST_HOPLHS)
    printf(", created indirectly via LHS operand in HOP/CLS/STO");
  else
    {
      printf(", created via \"");
      if (Symbol->RefType == ST_SYN)
        printf("SYN");
      else if (Symbol->RefType == ST_EQU)
        printf("EQU");
      else if (Symbol->RefType == ST_HOPC)
        printf("HOPC");
      printf(" %s\"", Symbol->RefName);
    }
}

/////////////////////////////////////////////////////////////////////////
// The main program.

int
main(int argc, char *argv[])
{
  int i, j, k, n, RetVal = 1;
  int m, p, s, w;
  uint16_t *u;
  FILE *OutFile;

  // Initialize the binary as completely unused.
  for (i = 0, u = &Binary[0][0][0][0]; i < MAX_ADDRESSES; i++, u++)
    *u = ILLEGAL_VALUE;

  // Parse the command-line options.
  for (i = 1; i < argc; i++)
    {
      if (!strcmp(argv[i], "--help"))
        {
          RetVal = 0;
          Help: ;
          fprintf(stderr, "USAGE:\n");
          fprintf(stderr, "\tyaASM [OPTIONS] --input=Input.obc >Output.lst\n");
          fprintf(stderr, "The binary (if any) is output to yaASM.bin.\n");
          fprintf(stderr, "The available OPTIONS are:\n");
          fprintf(stderr, "--help          Shows this help-menu.\n");
          fprintf(stderr, "--hwm           Start assembly in \"half-word\n");
          fprintf(stderr, "                mode\". (HALF or NORM directives\n");
          fprintf(stderr, "                within the source code itself can\n");
          fprintf(stderr, "                change the mode.)\n");
          fprintf(stderr,
              "--code=M-P-S-W  Starting address for instructions.\n");
          fprintf(stderr,
              "                M is the module number (0-7). P is\n");
          fprintf(stderr,
              "                the page number in octal (0-17). S is\n");
          fprintf(stderr,
              "                the syllable number (0,1,2). W is the\n");
          fprintf(stderr,
              "                word number in octal (0-377).  CODE\n");
          fprintf(stderr,
              "                directives embedded within the source\n");
          fprintf(stderr,
              "                code can change this.\n");
          fprintf(stderr,
              "--data=M-P-S-W  Starting address for data. The same\n");
          fprintf(stderr,
              "                interpretations apply as for --code, except\n");
          fprintf(stderr,
              "                that for the OBC S=2 is legal only with\n");
          fprintf(stderr,
              "                --hwm and S=0 is legal only without --hwm,\n");
          fprintf(stderr,
              "                while S=1 is never legal.  DATA directives\n");
          fprintf(stderr,
              "                within the source code can change the data\n");
          fprintf(stderr, "                pointer as well.\n");
          goto Done;
        }
      else if (!strncmp(argv[i], "--input=", 8))
        Input = &argv[i][8];
      else if (!strcmp(argv[i], "--hwm"))
        HalfWordMode = 1;
      else if (4 == sscanf(argv[i], "--code=%d-%d-%d-%d", &m, &p, &s, &w))
        {
          StartingInstructionPointer.Module = m;
          StartingInstructionPointer.Page = p;
          StartingInstructionPointer.Syllable = s;
          StartingInstructionPointer.Word = w;
          goto ParseAddressForError;
        }
      else if (4 == sscanf(argv[i], "--data=%d-%d-%d-%d", &m, &p, &s, &w))
        {
          StartingDataPointer.Module = m;
          StartingDataPointer.Page = p;
          StartingDataPointer.Syllable = s;
          StartingDataPointer.Word = w;
          ParseAddressForError: ;
          //if (m != 0)
          //  {
          //    fprintf(stderr, "For OBC, module number must be 0.\n");
          //    goto Help;
          //  }
          if (m < 0 || m >= MAX_MODULES)
            {
              fprintf(stderr, "Module number out of range.\n");
              goto Help;
            }
          if (p < 0 || p >= MAX_SECTORS)
            {
              fprintf(stderr, "Sector number out of range.\n");
              goto Help;
            }
          if (s < 0 || s >= MAX_SYLLABLES)
            {
              fprintf(stderr, "Syllable number out of range.\n");
              goto Help;
            }
          if (w < 0 || w >= MAX_WORDS)
            {
              fprintf(stderr, "Word-number out of range.\n");
              goto Help;
            }
        }
      else
        {
          fprintf(stderr, "Unrecognized option: %s\n", argv[i]);
          goto Help;
        }
    }
  if (HalfWordMode && DataPointer.Syllable != 2)
    {
      fprintf(stderr, "Syllable must be 2 for data in half-word mode.\n");
      goto Help;
    }
  if (!HalfWordMode && DataPointer.Syllable != 0)
    {
      fprintf(stderr,
          "Syllable must be 0 for data when OBC is not in half-word mode.\n");
      goto Help;
    }
  if (Input == NULL)
    {
      fprintf(stderr, "No input file specified.\n");
      goto Help;
    }
  fin = fopen(Input, "rb");
  if (fin == NULL)
    {
      fprintf(stderr, "Could not open input file %s\n", Input);
      goto Help;
    }

  // First pass on the input.  This pass simply determines the
  // memory locations for all left-hand symbols, variables, and
  // constants.
  if (Pass(PT_SYMBOLS))
    goto Done;

  // Let's check the symbol table for duplicates and sort for
  // faster access later.
  qsort(Symbols, NumSymbols, sizeof(Symbol_t), CompareSymbols);
  for (i = 1, j = 0; i < NumSymbols; i++)
    if (!strcmp(Symbols[i - 1].Name, Symbols[i].Name))
      {
        j++;
        printf(
            "The symbol %s had multiple definitions, at line %d and line %d.\r\n",
            Symbols[i].Name, Symbols[i - 1].Line, Symbols[i].Line);
        ErrorCount++;
      }
  if (j)
    goto Done;

  // Resolve all SYNs, EQUs, and HOPCs in the symbol table before proceeding.
  // Because of the way SYN works, we may not be able to resolve all symbols
  // on the first pass, so we keep performing passes until the final pass
  // didn't resolve any new symbols.  In this process, n is the number of symbols
  // resolved on the pass, j is the number of symbols left unresolved in the
  // pass, k counts the passes, and i is for looping through the symbols during
  // the pass.
  for (k = j = n = 1; n != 0 && j != 0; k++)
    {
      n = j = 0;
      for (i = 0; i < NumSymbols; i++)
        {
          enum SymbolType_t Type;
          Symbol_t *RefSymbol, Key;

          Type = Symbols[i].Type;
          if (Type != ST_SYN && Type != ST_EQU && Type != ST_HOPC)
            continue;
          strcpy(Key.Name, Symbols[i].RefName);
          RefSymbol = bsearch(&Key, Symbols, NumSymbols, sizeof(Symbol_t),
              CompareSymbols);
          if (RefSymbol == NULL || RefSymbol->Type == ST_SYN || RefSymbol->Type
              == ST_EQU || RefSymbol->Type == ST_HOPC)
            {
              j++;
              fprintf(
                  stderr,
                  "FYI: On pass %d, symbol %s referenced by symbol %s not resolved.\n",
                  k, Symbols[i].RefName, Symbols[i].Name);
              continue;
            }
          n = 1;
          switch (Type)
            {
          case ST_SYN:
            memcpy(&Symbols[i].Address, &RefSymbol->Address, sizeof(Address_t));
            Symbols[i].RefType = ST_SYN;
            Symbols[i].Type = RefSymbol->Type;
            Symbols[i].Value = RefSymbol->Value;
            break;
          case ST_EQU:
            if (RefSymbol->Type != ST_CONSTANT)
              {
                fprintf(stderr,
                    "EQU for %s->%s inappropriate since %s not a constant.\n",
                    Symbols[i].Name, Key.Name, Key.Name);
                goto Done;
              }
            Symbols[i].Type = ST_CONSTANT;
            Symbols[i].RefType = ST_EQU;
            Symbols[i].Value = RefSymbol->Value;
            if (WriteBinary(ST_CONSTANT, &Symbols[i].Address, Symbols[i].Value))
              {
                fprintf(stderr, "Aborting resolution of \"%s EQU %s\".\n",
                    Symbols[i].Name, Symbols[i].RefName);
                goto Done;
              }
            break;
          case ST_HOPC:
            if (RefSymbol->Type != ST_CODE)
              {
                fprintf(
                    stderr,
                    "EQU for %s->%s inappropriate since %s does not point to code.\n",
                    Symbols[i].Name, Key.Name, Key.Name);
                goto Done;
              }
            else
              {
                int OperandValue;
                Symbols[i].Type = ST_CONSTANT;
                Symbols[i].RefType = ST_HOPC;
                OperandValue = RefSymbol->Address.Word & 0377;
                if (RefSymbol->Address.HalfWordMode)
                  OperandValue |= 0400000;
                OperandValue |= (RefSymbol->Address.Syllable & 3) << 14;
                OperandValue |= (RefSymbol->Address.Page & 0x0F) << 9;
                if (RefSymbol->Address.Page == RESIDUAL_SECTOR)
                  OperandValue |= 0400;
                Symbols[i].Value = OperandValue;
                if (WriteBinary(ST_CONSTANT, &Symbols[i].Address,
                    Symbols[i].Value))
                  {
                    fprintf(stderr, "Aborting resolution of \"%s HOPC %s\".\n",
                        Symbols[i].Name, Symbols[i].RefName);
                    goto Done;
                  }
              }
            break;
            ;
          default: // Can't actually get here, due to a test made earlier.
            break;
            }
        }
    }
  fprintf(
      stderr,
      "FYI: %d symbol-resolution passes performed, %d symbols remain unresolved.\n",
      k - 1, j);
  if (j)
    goto Done;

  // Final pass on the input.  This pass generates the output
  // binary and listing.
  rewind(fin);
  if (Pass(PT_CODE))
    goto Done;

  // Output the symbol table.
  printf(NL);
  printf("ALPHABETIZED SYMBOL TABLE" NL);
  printf("-------------------------" NL);
  for (i = 0; i < NumSymbols; i++)
    {
      printf("%-8s at address ", Symbols[i].Name);
      printf("%o-", Symbols[i].Address.Module);
      printf("%02o-%o-%03o: ", Symbols[i].Address.Page,
          Symbols[i].Address.Syllable, Symbols[i].Address.Word);
      switch (Symbols[i].Type)
        {
      case ST_CODE:
        printf("Left-hand symbol");
        PrintRef(&Symbols[i]);
        if (!strcmp(Symbols[i].Name, "OBCENTRY"))
          memcpy(&ObcEntry, &Symbols[i].Address, sizeof(Address_t));
        break;
      case ST_VARIABLE:
        printf("Uninitialized variable");
        PrintRef(&Symbols[i]);
        break;
      case ST_CONSTANT:
        printf("Constant (%09o)", Symbols[i].Value);
        PrintRef(&Symbols[i]);
        break;
      case ST_SYN:
      case ST_EQU:
      case ST_HOPC:
        printf("Unresolved SYM, EQU, or HOPC (implementation error): %s",
            Symbols[i].RefName);
        break;
      default:
        printf("Unknown type (implementation error)");
        break;
        }
      printf(NL);
    }

  // Resort the symbol table by address and print it.
  qsort(Symbols, NumSymbols, sizeof(Symbol_t), CompareAddresses);
  printf(NL);
  printf("SYMBOL TABLE, BY ADDRESS" NL);
  printf("------------------------" NL);
  for (i = 0; i < NumSymbols; i++)
    {
      printf("%o-", Symbols[i].Address.Module);
      printf("%02o-%o-%03o, ", Symbols[i].Address.Page,
          Symbols[i].Address.Syllable, Symbols[i].Address.Word);
      printf("%-8s: ", Symbols[i].Name);
      switch (Symbols[i].Type)
        {
      case ST_CODE:
        printf("Left-hand symbol");
        PrintRef(&Symbols[i]);
        break;
      case ST_VARIABLE:
        printf("Uninitialized variable");
        PrintRef(&Symbols[i]);
        break;
      case ST_CONSTANT:
        printf("Constant (%09o)", Symbols[i].Value);
        PrintRef(&Symbols[i]);
        break;
      case ST_SYN:
      case ST_EQU:
      case ST_HOPC:
        printf("Unresolved SYM, EQU, or HOPC (implementation error): %s",
            Symbols[i].RefName);
        break;
      default:
        printf("Unknown type (implementation error)");
        break;
        }
      printf(NL);
    }

  // Binary listing.
  printf(NL);
  printf("OCTAL LISTING (SYL2-SYL1-SYL0)" NL);
  printf("------------------------------" NL);
  // In this loop, j is a state variable that we use to
  // avoid printing chunks of uninitialized memory.
  j = 0;
  for (m = 0; m < MAX_MODULES; m++)
    for (p = 0; p < MAX_SECTORS; p++)
      for (w = 0; w < MAX_WORDS; w++)
        {
          // We'll print 4 words per line.
          if (0 == (w & 3))
            {
              // Is the entire block uninitialized?
              for (s = 0; s < MAX_SYLLABLES; s++)
                for (k = 0; k < 4; k++)
                  if (Binary[m][p][s][w + k] != ILLEGAL_VALUE)
                    {
                      s = 100;
                      k = 100;
                    }
              if (s >= 100)
                j = 0;
              else
                {
                  if (j)
                    {
                      w += 3;
                      continue; // No need to print anything for this block.
                    }
                  j = 1;
                }
              printf("%o-", m);
              printf("%02o-N-%03o:", p, w);
              if (j)
                {
                  printf(
                      "  ----------------------------- (uninitialized) ----------------------------" NL);
                  w += 3;
                  continue;
                }
            }
          printf("  ");
          if (Binary[m][p][2][w] == ILLEGAL_VALUE)
            printf("XXXXX");
          else
            printf("%05o", Binary[m][p][2][w]);
          printf("-");
          if (Binary[m][p][1][w] == ILLEGAL_VALUE)
            printf("XXXXX");
          else
            printf("%05o", Binary[m][p][1][w]);
          printf("-");
          if (Binary[m][p][0][w] == ILLEGAL_VALUE)
            printf("XXXXX");
          else
            printf("%05o", Binary[m][p][0][w]);
          if (3 == (w & 3))
            printf(NL);
        }

  // Output the binary for use by the emulator if/when it's created.
  OutFile = fopen("yaASM.bin", "wb");
  if (OutFile == NULL)
    {
      fprintf(stderr, "Error: Cannot create the output file yaASM.bin.\n");
      goto Done;
    }
  else
    {
      uint32_t HopRegister = 0, Accumulator = 0, PqRegister = 0;
      HopRegister = ObcEntry.Word & 0777;
      HopRegister |= (ObcEntry.Page & 017) << 9;
      HopRegister |= (ObcEntry.Syllable & 03) << 14;
      HopRegister |= (ObcEntry.HalfWordMode & 01) << 17;
      if (1 != fwrite(Binary, sizeof(Binary), 1, OutFile) || 1 != fwrite(
          &HopRegister, sizeof(HopRegister), 1, OutFile) || 1 != fwrite(
          &Accumulator, sizeof(Accumulator), 1, OutFile) || 1 != fwrite(
          &PqRegister, sizeof(PqRegister), 1, OutFile))
        {
          fclose(OutFile);
          fprintf(stderr, "Error: Write-error on output file yaASM.bin.\n");
          goto Done;
        }
      fclose(OutFile);
    }

  if (ErrorCount == 0)
    RetVal = 0;
  Done: ;
  if (fin != NULL)
    fclose(fin);
  printf("\r\n%d error(s) detected.\r\n", ErrorCount);
  return (RetVal);
}

/////////////////////////////////////////////////////////////////////////
// Perform a single pass on the source code.

static int
Pass(enum PassType_t PassType)
{
  int i, RetVal = 1, OperandIsInteger, OperandValue;
  double fOperandValue;
  enum Operator_t Operator;
  //char c, *s, *ss;
  char *sss;
  Address_t *Address; // A dummy.

  memcpy(&InstructionPointer, &StartingInstructionPointer, sizeof(Address_t));
  memcpy(&DataPointer, &StartingDataPointer, sizeof(Address_t));
  LineTotal = LineInFile = 0;

  if (PassType == PT_CODE)
    {
      time_t t;
      time(&t);
      printf(
          "Listing created by OBC assembler yaASM rev " REVISION NL);
      printf("Source file %s processed %s" NL, Input, (char *) ctime(&t));
    }

  while (1)
    {
      int Commented = 0, LeftHandSymboled = 0, Operatored = 0, CurrentField = 0;
      enum SymbolType_t SymbolType = ST_NONE;
      char FirstChar;
      char OperandBuffer[MAX_SYMSIZE + 1], *OperandField;

      InputLine[LINESIZE - 1] = 127; // 20201206 RSB, was 255.
      if (NULL == fgets(InputLine, sizeof(Line_t), fin)) // Done with this file?

        {
          if (CurrentFileDepth == 0) // Done with ALL files?
            break;
          fclose(fin);
          // Pop parent file and proceed with processing it.
          CurrentFileDepth--;
          fin = Files[CurrentFileDepth].fin;
          Input = Files[CurrentFileDepth].Input;
          LineInFile = Files[CurrentFileDepth].LineInFile;
          continue;
        }

      LineTotal++;
      LineInFile++;
      if (InputLine[LINESIZE - 1] == 0)
        {
          //fprintf(stderr, "%s:%d: error: Line too long.\n", Input, LineInFile);
          //goto Done;
          printf("%s:%d: error: Line too long.\r\n", Input, LineInFile);
          ErrorCount++;
        }
      FirstChar = InputLine[0];

      sss = strstr(InputLine, "\n"); // Trim off trailing '\n'.
      if (sss != NULL)
        *sss = 0;
      sss = strstr(InputLine, "\r"); // Trim off trailing '\r'.
      if (sss != NULL)
        *sss = 0;

      sss = strstr(InputLine, "#"); // Pick off obvious comments;
      if (sss != NULL)
        {
          strcpy(Comment, sss + 1);
          *sss = 0;
          Commented = 1;
        }

      // Parse all of the fields in the input line.
      ParseToFields();

      // Pick off the first field in the line.
      if (NumFields == 0) // Blank line?

        {
          if (PassType == PT_CODE)
            {
              if (Commented)
                {
                  printf("   ");
                  if (FirstChar == '#')
                    printf("                   \t#%s", Comment);
                  else
                    printf(
                        "                   \t                               \t#%s",
                        Comment);
                }
              printf(NL);
            }
          continue;
        }
      CurrentField = 0;

      // Is this a file-include directive?
      if (Fields[CurrentField][0] == '$')
        {
          if (strlen(Fields[CurrentField]) == 1)
            {
              //fprintf(stderr, "%s:%d: error: No include-file specified.\n",
              //    Input, LineInFile);
              //goto Done;
              printf("%s:%d: error: No include-file specified.\r\n", Input,
                  LineInFile);
              ErrorCount++;
            }
          // Push current file and open the include-file instead.
          if (CurrentFileDepth >= MAXFILEDEPTH)
            {
              printf("%s:%d: error: Too many nested include-files.\r\n", Input,
                  LineInFile);
              ErrorCount++;
              fprintf(stderr, "%s:%d: error: Too many nested include-files.\n",
                  Input, LineInFile);
              goto Done;
            }
          Files[CurrentFileDepth].LineInFile = LineInFile;
          Files[CurrentFileDepth].fin = fin;
          strcpy(Files[CurrentFileDepth].Input, Input);
          CurrentFileDepth++;
          Input = &Fields[CurrentField][1];
          // Check what's left of the line.
          CurrentField++;
          if (CurrentField < NumFields)
            {
              if (Commented)
                fprintf(
                    stderr,
                    "%s:%d: warning: Garbage between include-directive and comment: %s\n",
                    Input, LineInFile, FieldStarts[CurrentField]);
              else
                {
                  strcpy(Comment, FieldStarts[CurrentField]);
                  Commented = 1;
                }
            }
          if (PassType == PT_CODE)
            {
              // ... output to listing ...
            }
          fin = fopen(Input, "rb");
          LineInFile = 0;
          if (fin == NULL)
            {
              printf(
                  "%s:%d: error: Include-file %s not found or too many files open.\r\n",
                  Input, LineInFile, Input);
              ErrorCount++;
              fprintf(
                  stderr,
                  "%s:%d: error: Include-file %s not found or too many files open.\n",
                  Input, LineInFile, Input);
              goto Done;
            }
          continue;
        }

      // Is this a pointer change?
      if (!strcmp(Fields[0], "HALF"))
        {
          HalfWordMode = 1;
          InstructionPointer.HalfWordMode = HalfWordMode;
          if (PassType == PT_CODE)
            {
              printf("   ");
              printf("                   \t         %s" NL, Fields[0]);
            }
          continue;
        }
      if (!strcmp(Fields[0], "NORM"))
        {
          HalfWordMode = 0;
          InstructionPointer.HalfWordMode = HalfWordMode;
          if (PassType == PT_CODE)
            {
              printf("   ");
              printf("                   \t         %s" NL, Fields[0]);
            }
          continue;
        }
      if (!strcmp(Fields[0], "DATA") || !strcmp(Fields[0], "CODE"))
        {
          int m, p, s, w;
          if (NumFields < 2)
            {
              //fprintf(stderr,
              //    "%s:%d: error: DATA or CODE require an operand.\n", Input,
              //    LineInFile);
              //goto Done;
              printf("%s:%d: error: DATA or CODE require an operand.\r\n",
                  Input, LineInFile);
              ErrorCount++;
              strcpy(Fields[1], "0-00-0-000");
              NumFields++;
            }
          if (NumFields > 2)
            {
              //fprintf(stderr,
              //    "%s:%d: error: Garbage following operand for DATA/CODE.\n",
              //    Input, LineInFile);
              //goto Done;
              printf(
                  "%s:%d: error: Garbage following operand for DATA/CODE.\r\n",
                  Input, LineInFile);
              ErrorCount++;
              NumFields = 2;
            }
          if (4 != sscanf(Fields[1], "%o-%o-%o-%o", &m, &p, &s, &w))
            {
              //fprintf(stderr,
              //    "%s:%d: error: Operand for DATA/CODE needs 4 fields.\n",
              //    Input, LineInFile);
              //goto Done;
              printf("%s:%d: error: Operand for DATA/CODE needs 4 fields.\r\n",
                  Input, LineInFile);
              ErrorCount++;
              m = p = s = w = 0;
            }
          if (m < 0 || m >= MAX_MODULES || p < 0 || p >= MAX_SECTORS || s < 0
              || s > 3 || w < 0 || w > MAX_WORDS)
            {
              //fprintf(stderr,
              //    "%s:%d: error: Operand for DATA/CODE out of range.\n",
              //    Input, LineInFile);
              //goto Done;
              printf("%s:%d: error: Operand for DATA/CODE out of range.\r\n",
                  Input, LineInFile);
              ErrorCount++;
              m = p = s = w = 0;
            }
          if (!strcmp(Fields[0], "CODE"))
            {
              InstructionPointer.Module = m;
              InstructionPointer.Page = p;
              InstructionPointer.Syllable = s;
              InstructionPointer.Word = w;
              InstructionPointer.HalfWordMode = HalfWordMode;
            }
          else
            {
              if (s == 1)
                {
                  //fprintf(stderr,
                  //    "%s:%d: error: Data syllable for OBC must be 0 or 2.\n",
                  //    Input, LineInFile);
                  //goto Done;
                  printf(
                      "%s:%d: error: Data syllable for OBC must be 0 or 2.\r\n",
                      Input, LineInFile);
                  ErrorCount++;
                  s = 0;
                }
              DataPointer.Module = m;
              DataPointer.Page = p;
              DataPointer.Syllable = s;
              DataPointer.Word = w;
              DataPointer.HalfWordMode = HalfWordMode;
              HalfWordMode = s ? 1 : 0;
            }
          if (PassType == PT_CODE)
            {
              printf("   ");
              printf("                   \t         %s ", Fields[0]);
              printf("%o-", m);
              printf("%02o-%o-%03o" NL, p, s, w);
            }
          continue;
        }

      // Does the line begin with a left-hand symbol or with an operator?
      // The difference is that an operator is one of the reserved words.
      for (Operator = OP_START; Operator < OP_COUNT; Operator++)
        if (!strcmp(Fields[CurrentField], Operators[Operator]))
          break;
      if (Operator >= OP_COUNT) // Must be a left-hand symbol.

        {
          LeftHandSymboled = 1;
          if (PassType == PT_SYMBOLS)
            {
              // Could be a duplicate symbol, but I'll do a separate check
              // for that later.  For now, just go ahead and add it to
              // the symbol list.
              if (strlen(Fields[CurrentField]) > MAX_SYMSIZE)
                {
                  //fprintf(
                  //    stderr,
                  //    "%s:%d: error: Left-hand symbol %s has too many characters.\n",
                  //    Input, LineInFile, Fields[CurrentField]);
                  //goto Done;
                  printf(
                      "%s:%d: error: Left-hand symbol %s has too many characters.\r\n",
                      Input, LineInFile, Fields[CurrentField]);
                  ErrorCount++;
                  Fields[CurrentField][MAX_SYMSIZE] = 0;
                }
              if (NumSymbols >= MAXSYMBOLS)
                {
                  printf("%s:%d: error: Max symbol-table size exceeded.\r\n",
                      Input, LineInFile);
                  ErrorCount++;
                  fprintf(stderr,
                      "%s:%d: error: Max symbol-table size exceeded.\n", Input,
                      LineInFile);
                  goto Done;
                }
              Symbols[NumSymbols].Line = LineTotal;
              strcpy(Symbols[NumSymbols].Name, Fields[CurrentField]);
              strcpy(Symbols[NumSymbols].RefName, "");
              Symbols[NumSymbols].RefType = ST_NONE;
              Symbols[NumSymbols].Value = 0xFFFFFFFF; // An invalid value.
              // There are a couple of more fields we have to fill in before
              // incrementing NumSymbols, but we can't do it quite yet without
              // determining the nature of the next input field.
            }
          CurrentField++;
        }

      // At this point, we must be out of fields (in which case this is a
      // variable allocation) or else must be the name of an opcode or pseudo-op.
      // (I arbitrarily disallow comments in variable allocations not having a
      // leading #, since otherwise it might be possible for s[] to be the leading
      // word of a comment.)
      if (CurrentField >= NumFields)
        {
          if (LeftHandSymboled)
            {
              SymbolType = ST_VARIABLE;
            }
        }
      else
        {
          Operatored = 1;
          for (Operator = OP_START; Operator < OP_COUNT; Operator++)
            if (!strcmp(Fields[CurrentField], Operators[Operator]))
              break;
          if (Operator < OP_OPCODES)
            SymbolType = ST_CODE;
          else if (Operator < OP_ALLOCS)
            {
              SymbolType = ST_CONSTANT;
            }
          else if (Operator == OP_SYN)
            {
              SymbolType = ST_SYN;
            }
          else if (Operator == OP_EQU)
            {
              SymbolType = ST_EQU;
            }
          else if (Operator == OP_HOPC)
            {
              SymbolType = ST_HOPC;
            }
          else
            {
              //fprintf(stderr, "%s:%d: error: Unrecognized operator %s.\n",
              //    Input, LineInFile, Fields[CurrentField]);
              //goto Done;
              printf("%s:%d: error: Unrecognized operator %s.\r\n", Input,
                  LineInFile, Fields[CurrentField]);
              ErrorCount++;
              SymbolType = ST_CODE;
              Operator = OP_NOP;
            }
        }

      // Finish making the symbol-table entry, if necessary.
      if (LeftHandSymboled && PassType == PT_SYMBOLS)
        {
          if (SymbolType == ST_CODE)
            memcpy(&Symbols[NumSymbols].Address, &InstructionPointer,
                sizeof(Address_t));
          else
            memcpy(&Symbols[NumSymbols].Address, &DataPointer,
                sizeof(Address_t));
          Symbols[NumSymbols].Type = SymbolType;
          NumSymbols++;
        }

      if (Operatored)
        {
          CurrentField++; // Move on to operand field, if any.
          // Except for NOP, every operator has a single operand,
          // so we can check at this point for non-existence (though
          // not correctness) of operands, as well as get any
          // remaining comment.
          if (SymbolType == ST_CODE && Operator == OP_NOP)
            /* CurrentField-- */;
          else
            {
              if (CurrentField >= NumFields)
                {
                  //fprintf(stderr, "%s:%d: error: Missing operand for %s.\n",
                  //    Input, LineInFile, Fields[CurrentField - 1]);
                  //goto Done;
                  printf("%s:%d: error: Missing operand for %s.\r\n", Input,
                      LineInFile, Fields[CurrentField - 1]);
                  ErrorCount++;
                  SymbolType = ST_CODE;
                  Operator = OP_NOP;
                }
            }
          if (CurrentField + 1 < NumFields) // Comment?

            {
              if (Commented)
                {
                  //fprintf(stderr, "%s:%d: error: Garbage after operand: %s.\n",
                  //    Input, LineInFile, FieldStarts[CurrentField + 1]);
                  //goto Done;
                  printf("%s:%d: error: Garbage after operand: %s.\r\n", Input,
                      LineInFile, FieldStarts[CurrentField + 1]);
                  ErrorCount++;
                  NumFields = CurrentField + 1;
                }
              strcpy(Comment, FieldStarts[CurrentField + 1]);
              Commented = 1;
            }
        }

      // Perform whatever specific processing is needed for the assembly pass.
      switch (SymbolType)
        {
      case ST_VARIABLE:
        // There's really nothing to do here, regardless of what pass this
        // is, since a variable allocation does nothing more than advance
        // the location pointer.
        if (DataPointer.Word > 255)
          {
            //fprintf(stderr,
            //    "%s:%d: error: Variable allocation past end of page.\n", Input,
            //    LineInFile);
            //goto Done;
            printf("%s:%d: error: Variable allocation past end of page.\r\n",
                Input, LineInFile);
            ErrorCount++;
            DataPointer.Word = 255;
          }
        if (PassType == PT_CODE)
          {
            // Write to the listing.
            printf("%o-", DataPointer.Module);
            //else
            //  printf("   ");
            printf("%02o-%o-%03o ", DataPointer.Page, DataPointer.Syllable,
                DataPointer.Word);
            printf("          ");
            printf("\t%-8s ", Fields[0]);
            printf("(ALLOCATION)          ");
            if (Commented)
              printf("\t#%s", Comment);
            printf(NL);
          }
        DataPointer.Word++;
        break;
      case ST_SYN:
      case ST_EQU:
      case ST_HOPC:
        if (!LeftHandSymboled)
          {
            //fprintf(
            //    stderr,
            //    "%s:%d: error: SYN, EQU, or SYN meaningful only with left-hand symbol.\n",
            //    Input, LineInFile);
            //goto Done;
            printf(
                "%s:%d: error: SYN, EQU, or SYN meaningful only with left-hand symbol.\r\n",
                Input, LineInFile);
            ErrorCount++;
            break;
          }
        // The operand is the name of another symbol.  We can't resolve
        // that just yet, since we don't have a complete symbol table
        // to work with.  What we'll do right now is simply to record
        // in the symbol table that the new symbol refers to the old symbol.
        // We'll resolve all these references by processing between the
        // 1st pass (PT_SYMBOLS) and the 2nd pass (PT_CODE) and replace them
        // with actual OP_VARIABLE, OP_CONSTANT, etc.
        if (PassType == PT_SYMBOLS)
          strcpy(Symbols[NumSymbols - 1].RefName, Fields[CurrentField]);
        if (PassType == PT_CODE)
          {
            Symbol_t Key, *OurLeftHandSymbol;
            strcpy(Key.Name, Fields[0]);
            OurLeftHandSymbol = bsearch(&Key, Symbols, NumSymbols,
                sizeof(Symbol_t), CompareSymbols);
            // Print to listing.  The binary was written in between
            // the PT_SYMBOLS and PT_CODE.
            printf("%o-", OurLeftHandSymbol->Address.Module);
            //else
            //  printf("   ");
            printf("%02o-%o-%03o ", OurLeftHandSymbol->Address.Page,
                OurLeftHandSymbol->Address.Syllable,
                OurLeftHandSymbol->Address.Word);
            if (OurLeftHandSymbol->Type == ST_CONSTANT)
              {
                if (OurLeftHandSymbol->Address.Syllable == 2)
                  printf("    %05o ", OurLeftHandSymbol->Value);
                else
                  printf("%09o ", OurLeftHandSymbol->Value);
              }
            else
              printf("          ");
            printf("\t%-8s ", Fields[0]);
            printf("%-4s ", Operators[Operator]);
            printf("%-16s ", Fields[CurrentField]);
            if (Commented)
              printf("\t#%s", Comment);
            printf(NL);
          }
        if (Operator == OP_SYN)
          {
            // Don't need to increment address pointer here.
          }
        else if (Operator == OP_EQU || Operator == OP_HOPC)
          {
            DataPointer.Word++;
          }
        break;
      case ST_CONSTANT:
        // In this case, we either have a DEC or OCT pseudo-op.
        if (DataPointer.Word > 255)
          {
            //fprintf(stderr,
            //    "%s:%d: error: Constant allocation past end of page.\n", Input,
            //    LineInFile);
            //goto Done;
            printf("%s:%d: error: Constant allocation past end of page.\r\n",
                Input, LineInFile);
            ErrorCount++;
            DataPointer.Word = 255;
          }
        switch (Operator)
          {
        case OP_OCT:
          OperandIsInteger = 1;
          sss = Fields[CurrentField];
          for (; *sss >= '0' && *sss <= '7'; sss++)
            ;
          if (*sss)
            {
              //fprintf(stderr, "%s:%d: error: Not an octal number: %s.\n",
              //    Input, LineInFile, Fields[CurrentField]);
              //goto Done;
              printf("%s:%d: error: Not an octal number: %s.\r\n", Input,
                  LineInFile, Fields[CurrentField]);
              ErrorCount++;
              OperandValue = 0;
            }
          else
            sscanf(Fields[CurrentField], "%o", &OperandValue);
          if (OperandValue >= (2 << 26))
            {
              //fprintf(stderr,
              //    "%s:%d: error: Octal constant out-of-range: %s.\n", Input,
              //    LineInFile, Fields[CurrentField]);
              //goto Done;
              printf("%s:%d: error: Octal constant out-of-range: %s.\r\n",
                  Input, LineInFile, Fields[CurrentField]);
              ErrorCount++;
              OperandValue = 0;
            }
          break;
        case OP_DEC:
          OperandIsInteger = 1;
          sss = Fields[CurrentField];
          if (*sss == '+' || *sss == '-')
            sss++;
          for (; isdigit (*sss); sss++)
            ;
          if (*sss)
            {
              if (*sss == '.')
                {
                  sss++;
                  OperandIsInteger = 0;
                }
              for (; isdigit (*sss); sss++)
                ;
              if (*sss)
                {
                  //fprintf(stderr, "%s:%d: error: Not a decimal number: %s.\n",
                  //    Input, LineInFile, Fields[CurrentField]);
                  //goto Done;
                  printf("%s:%d: error: Not a decimal number: %s.\r\n", Input,
                      LineInFile, Fields[CurrentField]);
                  ErrorCount++;
                  strcpy(Fields[CurrentField], "0");
                }
            }
          if (HalfWordMode)
            i = 12;
          else
            i = 25;
          if (OperandIsInteger)
            {
              sscanf(Fields[CurrentField], "%d", &OperandValue);
              if (OperandValue >= (2 << i) || (OperandValue < -(2 << i)))
                {
                  //fprintf(
                  //    stderr,
                  //    "%s:%d: error: Integer decimal constant out-of-range: %s.\n",
                  //    Input, LineInFile, Fields[CurrentField]);
                  //goto Done;
                  printf(
                      "%s:%d: error: Integer decimal constant out-of-range: %s.\r\n",
                      Input, LineInFile, Fields[CurrentField]);
                  ErrorCount++;
                  strcpy(Fields[CurrentField], "0");
                }
            }
          else
            {
              sscanf(Fields[CurrentField], "%lf", &fOperandValue);
              // Must scale so that 0.5<=|fOperandValue|<1.0.  Yes, there
              // are probably more-efficient ways to do it.  So what?
              for (; fabs(fOperandValue) >= 1.0; fOperandValue /= 2.0)
                ;
              if (fOperandValue != 0.0)
                for (; fabs(fOperandValue) < 0.5; fOperandValue *= 2.0)
                  ;
              OperandValue = lround(fOperandValue * (1 << i));
            }
          break;
        default: // Shouldn't be able to get here.
          //fprintf(stderr,
          //    "%s:%d: error: Implementation error, unparsed constant.\n",
          //    Input, LineInFile);
          //goto Done;
          printf("%s:%d: error: Implementation error, unparsed constant.\r\n",
              Input, LineInFile);
          ErrorCount++;
          OperandValue = 0;
          }
        // Having gotten to here, OperandValue contains a 2's-complement
        // value.
        OperandValue &= 0x3FFFFFF;
        if (LeftHandSymboled && PassType == PT_SYMBOLS)
          Symbols[NumSymbols - 1].Value = (unsigned) OperandValue;
        if (PassType == PT_CODE)
          {
            // Write to the binary.
            if (WriteBinary(ST_CONSTANT, &DataPointer, OperandValue))
              {
                //fprintf(stderr, "%s:%d: error: Aborting.\n", Input, LineInFile);
                //goto Done;
                printf("%s:%d: error: Binary-write error.\r\n", Input,
                    LineInFile);
                ErrorCount++;
              }
            // Write to the listing.
            printf("%o-", DataPointer.Module);
            //else
            //  printf("   ");
            printf("%02o-%o-%03o ", DataPointer.Page, DataPointer.Syllable,
                DataPointer.Word);
            if (DataPointer.Syllable == 2)
              printf("    %05o ", OperandValue);
            else
              printf("%09o ", OperandValue);
            if (LeftHandSymboled)
              printf("\t%-8s ", Fields[0]);
            else
              printf("\t         ");
            printf("%-4s ", Operators[Operator]);
            printf("%-16s ", Fields[CurrentField]);
            if (Commented)
              printf("\t#%s", Comment);
            printf(NL);
          }
        DataPointer.Word++;
        break;
      case ST_CODE:
        if (PassType == PT_SYMBOLS)
          {
            InstructionPointer.Word++;
            break;
          }
        // Process the individual code types. The opcode is given by
        // ParseTypes[Operator].NumericalOpCode, but the operand bits
        // need some interpreting.  We'll pack the results into 13 bits
        // of OperandValue.
        if (InstructionPointer.Word > 255)
          {
            //fprintf(stderr, "%s:%d: error: Code past end of page.\n", Input,
            //    LineInFile);
            //goto Done;
            printf("%s:%d: error: Code past end of page.\r\n", Input,
                LineInFile);
            ErrorCount++;
            InstructionPointer.Word = 255;
          }
        switch (ParseTypes[Operator].OperandType)
          {
        case OT_YX:
          // Here we expect two octal digits. Perhaps later I'll add some
          // range-checking on this, since not all values are legal for
          // all opcodes accepting this type of operand.  For now, I'll
          // just sleaze through and accept any values for X and Y.
          if (Fields[CurrentField][0] < '0' || Fields[CurrentField][0] > '7'
              || Fields[CurrentField][1] < '0' || Fields[CurrentField][1] > '7'
              || Fields[CurrentField][2] != 0)
            {
              //fprintf(stderr, "%s:%d: error: Not a legal YX operand.\n", Input,
              //    LineInFile);
              //goto Done;
              printf("%s:%d: error: Not a legal YX operand.\r\n", Input,
                  LineInFile);
              ErrorCount++;
              strcpy(Fields[CurrentField], "00");
            }
          sscanf(Fields[CurrentField], "%o", &OperandValue);
          break;
        case OT_CYX:
          {
            int Length, Bad;
            // Here we expect 2 *or* 3 octal digits. Perhaps later I'll add some
            // range-checking on this, since not all values are legal for
            // all opcodes accepting this type of operand.  For now, I'll
            // just sleaze through and accept any values for X and Y.  The
            // third octal digit, if present, has to be '4'.
            Length = strlen(Fields[CurrentField]);
            Bad = 0; // Initially, mark as "not bad", then perform tests.
            if (Length < 2 || Length > 3)
              Bad = 1; // Mark as "bad".
            else if (Fields[CurrentField][0] < '0' || Fields[CurrentField][0]
                > '7' || Fields[CurrentField][1] < '0'
                || Fields[CurrentField][1] > '7')
              Bad = 1;
            else if (Length == 3 && Fields[CurrentField][2] != '0'
                && Fields[CurrentField][2] != '4')
              Bad = 1;
            if (Bad)
              {
                //fprintf(stderr, "%s:%d: error: Not a legal operand.\n", Input,
                //    LineInFile);
                //goto Done;
                printf("%s:%d: error: Not a legal operand.\r\n", Input,
                    LineInFile);
                ErrorCount++;
                OperandValue = 0;
              }
            else
              sscanf(Fields[CurrentField], "%o", &OperandValue);
          }
          break;
        case OT_NONE:
          OperandValue = 0;
          break;
        case OT_SHR:
          if (!strcmp(Fields[CurrentField], "1"))
            OperandValue = 021;
          else if (!strcmp(Fields[CurrentField], "2"))
            OperandValue = 020;
          else
            {
              //fprintf(stderr, "%s:%d: error: SHR operand must be 1 or 2.\n",
              //    Input, LineInFile);
              //goto Done;
              printf("%s:%d: error: SHR operand must be 1 or 2.\r\n", Input,
                  LineInFile);
              ErrorCount++;
              OperandValue = 021;
            }
          break;
        case OT_SHL:
          if (!strcmp(Fields[CurrentField], "1"))
            OperandValue = 030;
          else if (!strcmp(Fields[CurrentField], "2"))
            OperandValue = 040;
          else
            {
              //fprintf(stderr, "%s:%d: error: SHL operand must be 1 or 2.\n",
              //    Input, LineInFile);
              //goto Done;
              printf("%s:%d: error: SHL operand must be 1 or 2.\r\n", Input,
                  LineInFile);
              ErrorCount++;
              OperandValue = 030;
            }
          break;
        case OT_NOP:
          OperandValue = InstructionPointer.Word + 1;
          break;
        case OT_ADDRESS:
          OperandField = Fields[CurrentField];
          // The HOP, CLA, and STO instructions present special difficulties,
          // for which we have to perform some pre-processing.  Even though
          // the operand for HOP is theoretically only a HOP constant, we
          // allow it to be a left-hand symbol as well, in which case we
          // allocate a HOP constant pointing to that left-hand symbol,
          // and of the same name (but enclosed in parentheses) and then
          // we substitute that newly-allocated HOP constant for the
          // original operand.  Similarly, CLA and STO require var/const
          // names, but we allow left-hand symbols there as well, allocating
          // the HOP-constants as just described.
          if (OperandField[0] != '*' && (ParseTypes[Operator].NumericalOpCode
              == OP_HOP || ParseTypes[Operator].NumericalOpCode == OP_CLA
              || ParseTypes[Operator].NumericalOpCode == OP_STO))
            {
              Symbol_t *Result, Key;
              char *ss;
              // Search for the symbol in the symbol table.
              strcpy(Key.Name, OperandField);
              Result = bsearch(&Key, Symbols, NumSymbols, sizeof(Symbol_t),
                  CompareSymbols);
              // The next block is to handle the special case in which
              // "STO (LHS)" or "CLA (LHS)" has been used prior
              // to a "HOP LHS" allocating the constant named
              // "(LHS)".
              if (Result == NULL && OperandField[0] == '(' && NULL != (ss
                  = strstr(OperandField, ")")) && ss > &OperandField[1])
                {
                  Symbol_t *Result2, Key2;
                  strncpy(Key2.Name, &OperandField[1], ss - &OperandField[1]);
                  Result2 = bsearch(&Key2, Symbols, NumSymbols,
                      sizeof(Symbol_t), CompareSymbols);
                  if (Result2 != NULL && Result2->Type == ST_CODE)
                    {
                      // So, if we've gotten to this point, the operand was
                      // "(something)", where "(something)" isn't already
                      // in the symbol table, but "something" is, and
                      // furthermore, that "something" is a left-hand symbol.
                      // If we simply replace the operand by "something",
                      // we should be able to fall through and let
                      // the next steps do the work of allocating "(something)".
                      strcpy(OperandField, Key2.Name);
                      Result = Result2;
                      memcpy(&Key, &Key2, sizeof(Key));
                    }
                }
              // The next block is the workhorse, executed when the
              // operand has been found and is a left-hand symbol
              // rather than data as would normally be required for
              // HOP, CLA, or STO.  What it has to do is to
              // allocate "(operand)" (if it does not already exist)
              // and doctor the instruction to use "(operand)" in
              // place of "operand".  The main processing can then
              // take it from there.
              if (Result != NULL && Result->Type == ST_CODE)
                {
                  Symbol_t *Result2, Key2;
                  if (strlen(OperandField) > MAX_SYMSIZE - 2)
                    {
                      //fprintf(
                      //    stderr,
                      //    "%s:%d: error: In this usage, the LHS length must be %d or less.\n",
                      //    Input, LineInFile, MAX_SYMSIZE - 2);
                      //goto Done;
                      printf(
                          "%s:%d: error: In this usage, the LHS length must be %d or less.\r\n",
                          Input, LineInFile, MAX_SYMSIZE - 2);
                      ErrorCount++;
                      OperandField[MAX_SYMSIZE - 2] = 0;
                    }
                  // It's possible that "(operand)" already
                  // exists, from some prior HOP/STO/CLA.
                  sprintf(Key2.Name, "(%s)", OperandField);
                  Result2 = bsearch(&Key2, Symbols, NumSymbols,
                      sizeof(Symbol_t), CompareSymbols);
                  if (Result2 == NULL) // Nope, not found.
                    {
                      int i, MaxWord = -1, OperandValue;
                      if (NumSymbols >= MAXSYMBOLS)
                        {
                          printf(
                              "%s:%d: error: Out of symbol space for HOP constant.\r\n",
                              Input, LineInFile);
                          ErrorCount++;
                          fprintf(
                              stderr,
                              "%s:%d: error: Out of symbol space for HOP constant.\n",
                              Input, LineInFile);
                          goto Done;
                        }
                      // Now we have a bit of a chore finding an unused
                      // address.  We basically have to search the entire
                      // symbol table to find the lowest unused data address
                      // in the current code sector, and then use that one.
                      // That guarantees that no variables or constants are
                      // already stored there, but it doesn't guarantee that
                      // there won't be a conflict with code that wants to
                      // go there.  Too bad!
                      for (i = 0; i < NumSymbols; i++)
                        if (Symbols[i].Address.Page == 017
                            && Symbols[i].Address.Syllable == 0)
                          if (Symbols[i].Address.Word > MaxWord)
                            MaxWord = Symbols[i].Address.Word;
                      MaxWord++;
                      if (MaxWord >= MAX_WORDS)
                        {
                          //fprintf(
                          //    stderr,
                          //    "%s:%d: error: Out of space in sector for HOP constant.\n",
                          //    Input, LineInFile);
                          //goto Done;
                          printf(
                              "%s:%d: error: Out of space in sector for HOP constant.\r\n",
                              Input, LineInFile);
                          ErrorCount++;
                          MaxWord--;
                        }
                      // In the sprintf below, the input and output strings have the
                      // same amount of space allocated to them, but the formatting
                      // adds two characters.  The .Name field is sized to accommodate
                      // this, but the compiler doesn't know this and detects a
                      // possible overflow.  The string 'keyname' is allocated with
                      // 2 less characters.
                      {
                        SymbolName_t keyname = { 0 };
                        strncpy(keyname, Key.Name, sizeof(keyname) - 1);
                        sprintf(Symbols[NumSymbols].Name, "(%s)", keyname);
                      }
                      Symbols[NumSymbols].Address.HalfWordMode = 0;
                      Symbols[NumSymbols].Address.Module = 0;
                      Symbols[NumSymbols].Address.Page = 017;
                      Symbols[NumSymbols].Address.Syllable = 0;
                      Symbols[NumSymbols].Address.Word = MaxWord;
                      Symbols[NumSymbols].Line = LineTotal;
                      strcpy(Symbols[NumSymbols].RefName, Key.Name);
                      Symbols[NumSymbols].Type = ST_CONSTANT;
                      Symbols[NumSymbols].RefType = ST_HOPLHS;
                      OperandValue = Result->Address.Word & 0377;
                      if (Result->Address.HalfWordMode)
                        OperandValue |= 0400000;
                      OperandValue |= (Result->Address.Syllable & 3) << 14;
                      OperandValue |= (Result->Address.Page & 0x0F) << 9;
                      if (Result->Address.Page == RESIDUAL_SECTOR)
                        OperandValue |= 0400;
                      Symbols[NumSymbols].Value = OperandValue;
                      if (WriteBinary(ST_CONSTANT,
                          &Symbols[NumSymbols].Address,
                          Symbols[NumSymbols].Value))
                        {
                          fprintf(stderr,
                              "Aborting resolution of \"%s HOPC %s\".\n",
                              Symbols[i].Name, Symbols[i].RefName);
                          goto Done;
                        }
                      NumSymbols++;
                      qsort(Symbols, NumSymbols, sizeof(Symbol_t),
                          CompareSymbols);
                    }
                  sprintf(OperandBuffer, "(%s)", OperandField);
                  OperandField = OperandBuffer;
                }
            }
          // We accept two cases here:  Either the operand is of the form
          // *+LiteralOctalConstant or *-LiteralOctalConstant, or else it
          // is the name of an existing symbol.  The former is actually
          // possible only if the operand is supposed to be code.
          // In the latter case, we must also check if is the
          // right type of address (variable/constant vs. code) for the
          // opcode type, and whether it's in the current sector vs. the
          // residual sector (vs. an unreachable sector).
          if (ParseTypes[Operator].AddressType == AT_DATA)
            Address = &DataPointer;
          else if (ParseTypes[Operator].AddressType == AT_CODE)
            Address = &InstructionPointer;
          else
            {
              //fprintf(stderr, "%s:%d: error: Implementation error.\n", Input,
              //    LineInFile);
              //goto Done;
              printf("%s:%d: error: Implementation error.\r\n", Input,
                  LineInFile);
              ErrorCount++;
              Address = &DataPointer;
            }
          if (OperandField[0] == '*' && ParseTypes[Operator].AddressType
              == AT_CODE)
            {
              // Could do some checking here for garbage at the end, but
              // am too lazy.
              if (OperandField[1] == 0)
                OperandValue = Address->Word;
              else if (OperandField[1] == '+' && 1 == sscanf(&OperandField[2],
                  "%o", &OperandValue))
                OperandValue = Address->Word + OperandValue;
              else if (OperandField[1] == '-' && 1 == sscanf(&OperandField[2],
                  "%o", &OperandValue))
                OperandValue = Address->Word - OperandValue;
              else
                {
                  //fprintf(stderr,
                  //    "%s:%d: error: Illegal relative addressing.\n", Input,
                  //    LineInFile);
                  //goto Done;
                  printf("%s:%d: error: Illegal relative addressing.\r\n",
                      Input, LineInFile);
                  ErrorCount++;
                  OperandValue = Address->Word;
                }
              if (OperandValue < 0 || OperandValue > 255)
                {
                  //fprintf(stderr,
                  //    "%s:%d: error: Relative addressing past end of page.\n",
                  //    Input, LineInFile);
                  //goto Done;
                  printf(
                      "%s:%d: error: Relative addressing past end of page.\r\n",
                      Input, LineInFile);
                  ErrorCount++;
                  OperandValue = (OperandValue < 0) ? 0 : 255;
                }
            }
          else
            {
              Symbol_t *Result, Key, Dummy =
                { 0 };
              // Search for the symbol in the symbol table.
              strcpy(Key.Name, OperandField);
              Result = bsearch(&Key, Symbols, NumSymbols, sizeof(Symbol_t),
                  CompareSymbols);
              if (Result == NULL)
                {
                  //fprintf(stderr,
                  //    "%s:%d: error: Symbol used as operand not found.\n",
                  //    Input, LineInFile);
                  //goto Done;
                  printf("%s:%d: error: Symbol used as operand not found.\r\n",
                      Input, LineInFile);
                  ErrorCount++;
                  Result = &Dummy;
                }
              if ((ParseTypes[Operator].AddressType == AT_CODE && Result->Type
                  != ST_CODE)
                  || (ParseTypes[Operator].AddressType == AT_DATA
                      && Result->Type != ST_VARIABLE && Result->Type
                      != ST_CONSTANT))
                {
                  //fprintf(
                  //    stderr,
                  //    "%s:%d: error: Operand symbol is wrong type (code vs. data).\n",
                  //    Input, LineInFile);
                  //goto Done;
                  printf(
                      "%s:%d: error: Operand symbol is wrong type (code vs. data).\r\n",
                      Input, LineInFile);
                  ErrorCount++;
                }
              // Need to do some checking on syllable matches.
              if (HalfWordMode)
                {
                  //fprintf(stderr, "%s:%d: error: Half-word mode not implemented yet.\n", Input, LineInFile);
                  //goto Done;
                  printf(
                      "%s:%d: error: Half-word mode not implemented yet.\r\n",
                      Input, LineInFile);
                  ErrorCount++;
                }
              else
                {
                  if (Result->Type == ST_VARIABLE || Result->Type
                      == ST_CONSTANT)
                    {
                      if (Result->Address.Syllable != 0)
                        {
                          //fprintf(
                          //    stderr,
                          //    "%s:%d: error: Not in half-word mode, cannot access syllable 2 data.\n",
                          //    Input, LineInFile);
                          //goto Done;
                          printf(
                              "%s:%d: error: Not in half-word mode, cannot access syllable 2 data.\r\n",
                              Input, LineInFile);
                          ErrorCount++;
                        }
                    }
                  else
                    {
                      if (Result->Address.Syllable != Address->Syllable)
                        {
                          //fprintf(
                          //    stderr,
                          //    "%s:%d: error: Cannot TRA, TMI, or TNZ to code in different syllable.\n",
                          //    Input, LineInFile);
                          //goto Done;
                          printf(
                              "%s:%d: error: Cannot TRA, TMI, or TNZ to code in different syllable.\r\n",
                              Input, LineInFile);
                          ErrorCount++;
                        }
                    }
                }
              OperandValue = Result->Address.Word;
              if (Result->Address.Page == RESIDUAL_SECTOR)
                OperandValue |= 0400; // Set residual sector.

              else if (Result->Address.Page != Address->Page)
                {
                  //fprintf(
                  //    stderr,
                  //    "%s:%d: error: Operand symbol is in an inaccessible page.\n",
                  //    Input, LineInFile);
                  //goto Done;
                  printf(
                      "%s:%d: error: Operand symbol is in an inaccessible page.\r\n",
                      Input, LineInFile);
                  ErrorCount++;
                }
            }
          break;
        default:
          //fprintf(stderr, "%s:%d: error: Implementation error.\n", Input,
          //    LineInFile);
          //goto Done;
          printf("%s:%d: error: Implementation error.\r\n", Input, LineInFile);
          ErrorCount++;
          OperandValue = 0;
          }
        // Okay, OperandValue should now hold the A1-A9 field, so let's fill in the
        // opcode field.
        OperandValue |= (ParseTypes[Operator].NumericalOpCode << 9);
        if (WriteBinary(ST_CODE, &InstructionPointer, OperandValue))
          {
            //fprintf(stderr, "%s:%d: error: Aborting.\n", Input, LineInFile);
            //goto Done;
            printf("%s:%d: error: Write-binary error.\r\n", Input, LineInFile);
            ErrorCount++;
          }
        printf("%o-", InstructionPointer.Module);
        printf("%02o-%o-%03o ", InstructionPointer.Page,
            InstructionPointer.Syllable, InstructionPointer.Word);
        printf("    %05o ", OperandValue);
        if (LeftHandSymboled)
          printf("\t%-8s ", Fields[0]);
        else
          printf("\t         ");
        printf("%-4s ", Operators[Operator]);
        printf("%-16s ", Fields[CurrentField]);
        if (Commented)
          printf("\t#%s", Comment);
        printf(NL);
        InstructionPointer.Word++;
        break;
      default: // I don't think this can happen.
        //fprintf(stderr, "%s:%d: error: Implementation error, unparsed line.\n",
        //    Input, LineInFile);
        //goto Done;
        printf("%s:%d: error: Implementation error, unparsed line.\r\n", Input,
            LineInFile);
        ErrorCount++;
        }
    }

  RetVal = 0;
  Done: ;
  return (RetVal);
}
back to top