https://github.com/virtualagc/virtualagc
Raw File
Tip revision: 7a9ecb86ff67a3d695b32b6dd0f7365a42048516 authored by Ron Burkey on 30 December 2017, 02:10:37 UTC
Experimental stuff related to triggering 1201 or 1202 alarms on demand.
Tip revision: 7a9ecb8
listing2binsource.c
/*
 *
 * Copyright 2010 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:     listing2binsource.c
 *
 * Purpose:      This program assists in creating an executable
 *               binary from AGC programs like Luminary or Colossus.
 *               Unlike yaYUL (which processes the assembly language
 *               to product an executable) or oct2bin (which processes
 *               an octal listing to produce an executable),
 *               listing2binsource processes octal codes from the columns
 *               in an assembly listing (which are parallel to the
 *               assembly language). This is helpful for a listing
 *               like the Colossus237 assembly listing obtained
 *               from Fred Martin in which the octal-listing section
 *               is missing completely, and yet we still need an
 *               executable obtained separately from yaYUL in order
 *               to cross-check the data entry of the assembly language.
 *
 * Contact:      Ron Burkey <info@sandroid.org>
 *
 * Website:      http://www.ibiblio.org/apollo/index.html
 *
 * Mods:         2010-12-11 RSB    Began.
 *               2010-12-19 RSB    Added the synonyms :.& for
 *                                 the commands c, p, and a.
 *               2010-12-23 RSB    Now allow comments, prefixed
 *                                 with #.
 *               2010-12-25 RSB    Allow '+' in place of 'a' or
 *                                 '&', because Dragon
 *                                 NaturallySpeaking 11 no longer
 *                                 allows me to use '&' the way
 *                                 DNS 8 did.  Sigh ....
 *               2012-09-17 JL     Tidy up. Handle pages with just
 *                                 an address line. Handle lines
 *                                 with whitespace at the end.
 *                                 Fix handling of fixed-fixed 
 *                                 addresses. Take input and output
 *                                 filenames as arguments.
 *               2012-09-18 JL     Add bugger word generation. Add
 *                                 verbose option. Add debug prints.
 *               2017-01-30 MAS    Added 'y' (check parity) and 'n'
 *                                 (no banksums) options.
 *               2017-02-01 MAS    Changed UNASSIGNED words to print
 *                                 out @ instead of 00000.
 *
 * For listing2binsource.c, the octal codes are provided in an input file
 * created by moving through the assembly-language portion of the
 * assembly listing page by page and line by line.  The octal codes
 * are 5-digit octal numbers delimited either by commas or newlines
 * (whichever seems convenient at the time).  Interspersed with lines
 * containing octal codes are lines of one of the following forms:
 *
 * y           (Octals that follow have parity bits)
 * pN          (N being a 1-4 digit page number from the assembly listing)
 * aNNNN       (NNNN being a 4-digit octal number indicating the
 *             CPU location counter for the next section of octal codes)
 * aNN,NNNN    (Same, but with banked address NN,NNNN instead)
 * cNNNN       (The address of the preceding octal code)
 * cNN,NNNN    (Same, but with a banked address NN,NNNN instead)
 *
 * The following synonyms are also accepted:
 *   :    in place of     c
 *   .    in place of     p
 *   &    in place of     a
 *   +    in place of     a
 *
 * These synonyms can be remembered easily since "colon", "period"
 * (or "point"), or "and" or "add" (or "ampersand")  begin with the
 * same letter as the commands they're replacing.  The synonyms are
 * very convenient when dictating the octal listing using
 * Dragon NaturallySpeaking in "numbers" mode, since with these
 * synonyms (and the words "comma" and "newline") *all* of the
 * data-entry can be dictated without needing to use the keyboard
 * for anything other than corrections.
 *
 * The pN, cNNNN, and cNN,NNNN codes would not be necessary *if*
 * data entry is perfectly accurate, but are pragmatically necessary
 * for debugging data entry because they allow listing2binsource to
 * determine that the correct number of octal codes have been entered
 * for any given address range, and all warning messages to be printed
 * that localize such mismatches to the page.  It does not allow any
 * determination that the octal codes are *correct*, of course.
 * (Without an octal listing appearing in the assembly listing, the
 * memory-bank checksums are unknown.  They can be computed by
 * listing2binsource, but cannot be checked by listing2binsource.)  Thus,
 * while pN, cNNNN, and cNN,NNNN are optional, listing2binsource will
 * print warning messages wherever such a message would be useful
 * but is not found.  In particular:
 *
 *   - It is expected that pN lines appear in the order p1, p2, p3, ...
 *     pLAST throughout the input file, without any missing pages.  (If
 *     there are no octal codes on a page, its pN should appear anyway.)
 *
 *   - A cNNNN or cNN,NNNN should appear before every pN (except for
 *     the first page or if the preceding page contained no octal codes).
 *
 *   - A cNNNN or cNN,NNNN should appear before every aNNNN or aNN,NNNN
 *    (except those immediately preceded by a pN).
 *
 * Whitespace (including completely blank lines) is ignored by
 * listing2binsource, as are commas at the ends of lines.
 *
 * The input file is read on stdin, and a file capable of being used
 * as input to Oct2Bin is written on stdout.  printError and warning messages
 * are written to stderr.  The return value is zero on success, and
 * is non-zero if there were any warnings or errors.
 *
 */

#include <stdio.h>
#include <inttypes.h>
#include <string.h>
#include "utils.h"

int errorCount = 0;

static char inputLine[16384];
static int16_t rope[NUM_BANKS][WORDS_PER_BANK];

static int printError(int page, int line, char *message)
{
    fprintf(stderr, "Error (p%d, line %d): %s\n", page, line, message);
    return (1);
}

int main(int argc, char *argv[])
{
    int verbose = 0;
    int i, j;
    char c;
    int retval = 0;
    int line = 0;
    int inputLength;
    char *s, *ss;
    int currentPage = 0;
    int bank = -1, offset = -1;
    char lastLineType = 'c';
    int count = 0 /*, page = 2000 */;
    int useParity = 0;
    int noBanksums = 0;
    FILE *infile, *outfile;

    if (argc < 3) {
        fprintf(stderr, "Usage: listing2binsource [-v] infile outfile\n");
        return (1);
    }

    // Parse the command-line switches.
    if (!strcmp(argv[1], "--verbose") || !strcmp(argv[1], "-v")) {
        if (argc < 4) {
            fprintf(stderr, "Usage: listing2binsource [-v] infile outfile\n");
            return (1);
        }
        verbose = 1;
        i = 2;
    } else {
        i = 1;
    }

    if (verbose)
        printf("Opening input file %s...\n", argv[i]);
    infile = fopen(argv[i], "r");
    if (infile == NULL) {
        fprintf(stderr, "Error, could not open file \"%s\".\n", argv[i]);
        return (1);
    }

    i++;

    if (verbose)
        printf("Opening output file %s...\n", argv[i]);
    outfile = fopen(argv[i], "w");
    if (outfile == NULL) {
        fprintf(stderr, "Error, could not open file \"%s\".\n", argv[i]);
        if (infile)
            fclose(infile);
        return (1);
    }

    // Initialize the rope with the special value UNASSIGNED,
    // which can't occur in the 15-bit input.  UNASSIGNED will be
    // automatically converted to 00000 when the rope is saved
    // later, but will be a good internal marker for us that
    // a given word hasn't been assigned a value.
    if (verbose)
        printf("Initializing rope image...\n");
    for (i = 0; i < NUM_BANKS; i++)
        for (j = 0; j < WORDS_PER_BANK; j++)
            rope[i][j] = (int16_t)UNASSIGNED;

    if (verbose)
        printf("Processing input file...\n");
    // Now read the input file.
    while (fgets(inputLine, sizeof(inputLine), infile) != NULL) {
        line++;

        // Eliminate spaces, newlines, comments.
        for (s = ss = inputLine; *s; s++) {
            if (*s == '\n' || *s == '\r' || *s == '#')
                break;
            if (*s == ' ')
                continue; // Ignore all spaces.
            *ss++ = *s;
        }

        // Ignore completely empty lines.
        if (ss == inputLine)
            continue;

        // Make sure there's exactly one ',' at the end of the line
        // by appending ',' and then reducing all multiple commas at
        // the end of line to a single comma.
        *ss++ = ',';
        for (; ss >= &inputLine[1] && ss[-1] == ',' && ss[-2] == ','; ss--)
            ;

        inputLength = ss - inputLine;
        *ss = 0;

        // Translate some command synonyms:
        switch (inputLine[0]) {
        case ':':
            inputLine[0] = 'c';
            break;
        case '.':
            inputLine[0] = 'p';
            break;
        case '&':
            inputLine[0] = 'a';
            break;
        case '+':
            inputLine[0] = 'a';
            break;
        }

        // Now that the input lines have been completely normalized,
        // we can begin parsing them.
        if (inputLine[0] == 'y') {
            useParity = 1;
        } else if (inputLine[0] == 'n') {
            noBanksums = 1;
        } else if (inputLine[0] == 'p') {
            s = strstr(inputLine, ",");
            if ((s == &inputLine[inputLength - 1]) && (sscanf(inputLine, "p%d%c", &i, &c) == 2) && (c == ',')) {
                if (i != currentPage + 1)
                    retval = printError(currentPage, line, "Page number out of sequence.");
                if (lastLineType != 'c' && lastLineType != 'p' && lastLineType != 'a' && lastLineType != 'y' && lastLineType != 'n')
                    retval = printError(currentPage, line, "Missing address-check line.");
                currentPage = i;
            } else
                retval = printError(currentPage, line, "Ill-formed page line.");
        } else if (inputLine[0] == 'c' || inputLine[0] == 'a') {
            s = strstr(inputLine, ",");

            if (s[1] != 0)
                s = strstr(s + 1, ",");

            if ((s == &inputLine[inputLength - 1]) && (sscanf(&inputLine[1], "%o,%o%c", &i, &j, &c) == 3) && (c == ',')) {
ProcessAorC:
                if (i < 0 || i >= NUM_BANKS || j < BANK_OFFSET || j > BANK_OFFSET + WORDS_PER_BANK)
                    retval = printError(currentPage, line, "Address out of range.");

                j -= BANK_OFFSET;
                if (inputLine[0] == 'a') {
                    if (lastLineType != 'c' && lastLineType != 'p')
                        retval = printError(currentPage, line, "Missing address-check line.");
                    bank = i;
                    offset = j;
                } else {
                    // inputLine[0] == 'c'
                    if (bank != i || offset != j + 1) {
                        char msgStr[128];
                        sprintf(msgStr, "Address-check mismatch, expecting (%02o,%04o), got (%02o,%04o).",
                                bank, offset + BANK_OFFSET, i, j + 1 + BANK_OFFSET);
                        retval = printError(currentPage, line, msgStr);
                    }
                }
            } else if ((s == &inputLine[inputLength - 1]) && (sscanf(&inputLine[1], "%o%c", &j, &c) == 2) && (c == ',')) {
                if (j >= 04000 && j <= 05777) {
                    i = 2;
                    j -= BANK_OFFSET;
                } else if (j >= 06000 && j <= 07777) {
                    i = 3;
                    j -= BANK_OFFSET * 2;
                } else
                    i = -1;
                goto ProcessAorC;
            }

            else
                retval = printError(currentPage, line, "Ill-formed address or check line.");
        } else {
            // Must be octal codes.
            if (bank < 0 || bank >= NUM_BANKS || offset < 0 || offset >= WORDS_PER_BANK)
                retval = printError(currentPage, line, "Missing address assignment.");

            // Parse each field on the line.  Recall that the line ends with
            // a comma, so we can just search until there are no more commas.
            // This also handles the case of one word per line, which is an easier format
            // to enter if using the keyboard.
            for (s = inputLine; (ss = strstr(s, ",")) != NULL && ss < &inputLine[inputLength]; s = ss + 1) {
                if (sscanf(s, "%o%c", &i, &c) != 2 || (c != ',' && c != ' ' && c != '\t')) {
                    i = CORRUPTED;
                    retval = printError(currentPage, line, "Illegal characters in octal field.");
                }

                if (useParity) {
                    int parity = i & 07;
                    int word;
                    if (parity > 1) {
                        i = CORRUPTED;
                        retval = printError(currentPage, line, "Illegal parity digit (must be 0 or 1)");
                    }
                    i >>= 3;
                    // Check parity
                    word = i | (parity << 15);
                    word ^= (word >> 8);
                    word ^= (word >> 4);
                    word ^= (word >> 2);
                    word ^= (word >> 1);
                    word &= 1;
                    if (word != 1) {
                        i = CORRUPTED;
                        retval = printError(currentPage, line, "Parity error");
                    }
                }

                if (retval == 0 && (i < 0 || i > 077777)) {
                    i = CORRUPTED;
                    retval = printError(currentPage, line, "Bad octal field, greater than 5 digits.");
                }

                if (retval == 0 && (rope[bank][offset] >= 00000 && rope[bank][offset <= 077777])) {
                    i = OVERWRITTEN;
                    char msgStr[128];
                    sprintf(msgStr, "Memory overwrite at (%02o,%04o), existing contents: %05o.", bank, offset, rope[bank][offset]);
                    retval = printError(currentPage, line, msgStr);
                }

                if (retval == 0) {
                    rope[bank][offset] = i;
                }

                offset++;
            }
        }
        lastLineType = inputLine[0];
    }

    // Look for the first unassigned word at the end of the bank. Note that it is
    // possible for there to be unassigned words in the middle of a bank if some
    // words are unused (might be a transcription error?). Colossus237 banks 4 and
    // 23 illustrate this phenomenon.
    if (retval == 0 && !noBanksums) {
        for (i = 0; i < NUM_BANKS; i++) {
            int bank = (i < 4) ? (i ^ 2) : i;
            if (verbose)
                printf("Generating bugger word for bank %02o...\n", bank);
            for (j = WORDS_PER_BANK; j > 0; j--) {
                int16_t value = rope[bank][j-1];

                if (value != (int16_t)UNASSIGNED) {
                    if (verbose)
                        printf("Last used word in bank %02o is at offset %04o.\n", bank, BANK_OFFSET + j - 1);

                    int16_t bugger = generateBuggerWord(verbose, bank, j, &rope[bank][0]);
                    if (verbose)
                        printf("Bugger word %05o at (%02o,%04o).\n", bugger, bank, BANK_OFFSET + j);
                    rope[bank][j] = bugger;
                    break;
                }
            }
        }
    }

    // Write the output.  This code was swiped and slightly altered from oct2bin.
    if (retval == 0) {
        if (verbose)
            printf("Writing output...\n");
        fprintf(outfile, "; Copyright: Public domain\n");
        fprintf(outfile, "; Filename:  XXXX.binsource\n");
        fprintf(outfile, "; Purpose:   XXXX\n");
        fprintf(outfile, "; Contact:   info@sandroid.org\n");
        fprintf(outfile, "; Mods:      XXXX-XX-XX XXX    Auto-generated by listing2binsource.\n");
        fprintf(outfile, ";            XXXX-XX-XX XXX    XXXX\n");
        fprintf(outfile, ";\n");

        count = 0;

        // Read and write data.
        for (i = 0; i < NUM_BANKS; i++) {
            int bank = (i < 4) ? (i ^ 2) : i;
            char bankString[33];
            for (j = 0; j < WORDS_PER_BANK; j++) {
                int16_t value = rope[bank][j];
                if ((count % 8) == 0)
                    fprintf(outfile, "\n");

                if ((count % 32) == 0)
                    fprintf(outfile, "\n");

                if ((count % 256) == 0) {
                    if (count < 2048)
                        sprintf(bankString, " %05o", 04000 + count);
                    else
                        sprintf(bankString, " %02o,%04o", bank, BANK_OFFSET + (count % 1024));
                    fprintf(outfile, "; %s\n", bankString);
                }

                if ((count % 1024) == 0)
                    fprintf(outfile, "BANK=%o\n", bank);

                if (value >= 0)
                    fprintf(outfile, "%05o ", value);
                else
                    fprintf(outfile, "  @   ");
                count++;
            }
        }
    }

    return (retval);
}
back to top