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
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