ftp.nice.ch/pub/next/tools/emulators/vice.0.15.0.NeXT.sd.tgz#/vice-0.15.0/src/petcat.c

This is petcat.c in view mode; [Download] [Up]

/*
 * $Id: petcat.c,v 2.3 1997/05/04 16:23:04 ettore Exp ettore $
 *
 * This file is part of Commodore 64 emulator
 *      and Program Development System.
 *
 *   Copyright (C) 1993-1995,1996, Jouko Valta
 *
 *   This program 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.
 *
 *   This program 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 this program; if not, write to the Free Software
 *   Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
 *
 *
 * This program converts your SEQ files as well as expands tokenized
 * C64/128 BASIC programs into 7-bit ASCII text. Unprintable characters
 * can be shown as hexadecimal codes in parenthesis, via `-c' option.
 * It is also possible to convert programs from ascii listings into
 * tokenized basic v2.0, v3.5, v4.0, v7.0 or simon's basic programs. This
 * program also replaces certain control code names with the actual codes.
 *
 * A list of Toolkit Basic (published by Compute! Books) token codes
 * would be greatly appreciated. (-:
 *
 * Runs on UNIX or Atari ST.
 *
 * In shell:
 *  tr '\015A-Za-z\301-\332\\\|\[\{\]\}' '\012a-zA-ZA-Z\|\\\{\[\}\]'
 * or
 *  tr '\015A-Za-z\\\|\[\{\]\}' '\012a-zA-Z\|\\\{\[\}\]'
 *
 *
 * Written by
 *   Jouko Valta (jopi@stekt.oulu.fi)
 *
 *
 * $Log: petcat.c,v $
 * Revision 2.3  1997/05/04 16:23:04  ettore
 * Assignment of const pointer to non-const one fixed.
 *
 * Revision 2.2  1996/06/07  20:52:35  jopi
 * All conversions sped up significantly.
 *
 * Revision 2.1  1996/04/01  09:01:41  jopi
 * AtBasic added
 * P00 support implemented
 * offset for listing directly from archives.
 *
 * Revision 2.0  1995/06/28  19:48:14  jopi
 * Turtle Basic V1.0 and PET BASIC 1.0
 * CBM-x Graphic characters named
 *
 * Revision 1.9  1995/04/01  07:54:09  jopi
 * X64 0.3 PL 0
 * Suppressing header added.
 *
 * Revision 1.8  1994/12/12  16:59:44  jopi
 * PET Basic V4.0, Basic 10.0 and Speech Basic added.
 * New options for selecting version. Can set load address and
 * output file.
 *
 * Revision 1.7  1994/06/16  17:19:26  jopi
 * Alternate Control code set.
 *
 * Revision 1.6  1994/06/08  16:19:50  jopi
 * X64 version 0.2 PL 2
 * Operation improved, Basic 3.5 and new options.
 * Ported to Atari ST. Control code cruncher added.
 *
 * Revision 1.5  1994/01/26  16:08:37  jopi
 * X64 version 0.2 PL 1
 * Text mode, directory mode, Super Expander and Basic 4 ext. added.
 * Arithmetics tokenizing fixed, and crunches appreviated keywords too.
 *
 * Revision 1.4  1993/11/10  01:55:34  jopi
 * Text converter. Unused tokens skipped.
 *
 * Revision 1.3  93/06/21  13:38:03  jopi
 * X64 version 0.2 PL 0
 *
 * Revision 1.2  1993/06/13  08:19:36  sonninen
 * *** empty log message ***
 *
 *
 */

/* Ettore Perazzoli (ettore@comm2000.it) 1997/10/27: Renamed `p_toascii' to
   `_p_toascii' to avoid conflicts with `charsets.h'. */

#include "vice.h"

#include <stdio.h>
#include <stdlib.h>
#include <ctype.h>
#ifdef GEMDOS			/* Atari ST */
#define strchr index
#else
#include <string.h>
#endif

#include "charsets.h"		/* ctrl1, ctrl2, cbmkeys */

#ifndef SEEK_SET
#define SEEK_SET 0
#endif

#define PETCATVERSION	2.02
#define PETCATLEVEL	1

#define B_1		 1
#define B_2		 2
#define B_SUPER		 3
#define B_TURTLE	 4

#define B_SIMON		 5
#define B_SPEECH	 6
#define B_ATBAS		 7
#define B_4		 8
#define B_4E		 9	/* C64 extension, Expand only */

#define B_35		10
#define B_7		11
#define B_10		12


/* Limits */

#define NUM_B_1		75	/* Basic 1.0 */

#define NUM_COMM	76	/* common for all versions 2.0 ... 10.0 */
#define NUM_SECC	18	/* VIC-20 extension */
#define NUM_TUCC	34	/* VIC-20 Turtle Basic extension */

#define NUM_V4CC	15	/* PET 4.0 */
#define NUM_4ECC	24	/* 4.0 extension (C64) */
#define NUM_SPECC	27	/* Speech Basic */
#define NUM_ATBCC	43	/* Atbasic */

#define NUM_KWCE	11
#define NUM_V7FE	39
#define NUM_V10FE	62


#define MAX_COMM	0xCB	/* common for all versions */
#define MAX_SECC	0xDD	/* VIC-20 extension */
#define MAX_TUCC	0xED	/* VIC-20 Turtle Basic extension */

#define MAX_V4CC	0xDA	/* PET 4.0 */
#define MAX_4ECC	0xE3	/* 4.0 extension (C64) */
#define MAX_SPECC	0xE6	/* Speech Basic */
#define MAX_ATBCC	0xF6	/* Atbasic */

#define MAX_KWCE	0x0A
#define MAX_V7FE	0x26
#define MAX_V10FE	0x3D

#define KW_NONE		0xFE	/* flag unused token */


#define CLARIF_LP	'<'	/* control code left delimiter */
#define CLARIF_RP	'>'	/* control code right delimiter */


#if (!defined(GEMDOS) && defined(__STDC__))
static int    parse_version ( char *str );
static void   pet_2_asc ( int ctrls );
static void   _p_toascii ( int c, int ctrls);
static void   list_keywords ( int version );
static int    p_expand ( int version, int addr, int ctrls );
static void   p_tokenize ( int version, int addr, int ctrls );
static unsigned char sstrcmp ( unsigned char *, char**, int, int );

/* extern */
extern long filelength ( int handle );
extern int  is_pc64name ( char *name );
extern int  write_pc64header ( FILE *fd, char *name, int reclen );
extern int  read_pc64header ( FILE *fd, char *name, int *reclen );
extern char *pc_get_cbmname ( FILE *fd, char *fsname );
extern int  DirP00 ( FILE *fd, char *CbmName, int *start, int *end );

extern int  check_t64_header (FILE *f);

#else
static int    parse_version();
static void   pet_2_asc();
static void   _p_toascii();
static void   list_keywords();
static int    p_expand();
static void   p_tokenize();
static unsigned char sstrcmp();

/* extern */
extern long filelength ();
extern int  is_pc64name ();
extern int  write_pc64header ();
extern int  read_pc64header ();
extern char *pc_get_cbmname ();
extern int  DirP00 ();

extern int  check_t64_header (FILE *f);

#endif


/* ------------------------------------------------------------------------- */

/* PC64 files need this */

char *slot_type[] =
{
    "DEL", "SEQ", "PRG", "USR", "REL", "CBM", "DJJ", "FAB"
};

static const unsigned char MagicHeaderP00[8] = "C64File\0";


#define NUM_VERSIONS  12

char   *VersNames[] = {
    "Basic 1.0",
    "Basic 2.0",
    "Basic 2.0 with Super Expander",
    "Basic 2.0 with Turtle Basic v1.0",

    "Basic 2.0 and Simon's Basic",
    "Basic 2.0 with Speech Basic v2.7",
    "Basic 2.0 with @Basic",

    "Basic 4.0",
    "Basic 4.0 extension",
    "Basic 3.5",
    "Basic 7.0",
    "Basic 10.0",
    ""
};


/*
 * CBM Basic Keywords
 */

char   *keyword[] = {
    /* Common Keywords, 80 - cb */
    "end",   "for",  "next", "data", "input#", "input",  "dim", "read",
    "let",   "goto", "run",  "if",   "restore", "gosub", "return", "rem",
    "stop",  "on",   "wait", "load", "save",   "verify", "def", "poke",
    "print#", "print", "cont", "list", "clr",  "cmd", "sys", "open",
    "close", "get",  "new",  "tab(", "to",    "fn",  "spc(", "then",
    "not",   "step", "+",    "-",    "*",     "/",   "^",    "and",
    "or",    ">",    "=",    "<",    "sgn",   "int", "abs",  "usr",
    "fre",   "pos",  "sqr",  "rnd",  "log",   "exp", "cos",  "sin",
    "tan",   "atn",  "peek", "len",  "str$",  "val", "asc",  "chr$",
    "left$", "right$", "mid$", "go",
    /*
     * The following codes (0xcc- 0xfe) are for 3.5, 7.0, and 10.0 only.
     * On 10.0 gshape, sshape, and draw are replaced with paste, cut, and line
     * respectively. */
    "rgr",  "rlcr", "rlum" /* 0xce -- v7 prefix */, "joy",
    "rdot", "dec",  "hex$", "err$", "instr", "else", "resume", "trap",
    "tron", "troff", "sound", "vol", "auto", "pudef", "graphic", "paint",
    "char", "box", "circle", "gshape", "sshape", "draw", "locate", "color",
    "scnclr", "scale", "help", "do", "loop", "exit", "directory", "dsave",
    "dload",  "header", "scratch", "collect", "copy", "rename", "backup",
    "delete", "renumber", "key", "monitor", "using", "until", "while",
    /* 0xfe -- prefix */ "", "~" /* '~' is ASCII for 'pi' */
};


/*
 * 7.0 and 10.0 only.
 * On 10.0 stash, fetch, and swap are replaced with dma.
 */

char   *kwce[] = {
    "", "", "pot", "bump", "pen", "rsppos", "rsprite", "rspcolor",
    "xor", "rwindow", "pointer"
};


char   *kwfe[] = {
    "", "",  "bank", "filter",  "play", "tempo", "movspr", "sprite",
    "sprcolor", "rreg", "envelope", "sleep", "catalog", "dopen", "append",
    "dclose", "bsave",
     "bload", "record", "concat", "dverify", "dclear", "sprsav", "collision",
    "begin", "bend",  "window", "boot", "width", "sprdef", "quit", "stash",
    "",  "fetch", "",  "swap",  "off",  "fast",  "slow",
    /* Basic 10.0 only (fe27 - fe3d) */
    "type",
    "bverify", "ectory", "erase", "find", "change", "set", "screen", "polygon",
    "ellipse", "viewport", "gcopy", "pen", "palette", "dmode", "dpat", "pic",
    "genlock", "foreground", "", "background", "border", "highlight"
};


char  *superkwcc[] = { /* VIC Super Expander commands 0xcc - 0xdd */
    "key", "graphic", "scnclr", "circle", "draw", "region", "color", "point",
    "sound", "char",  "paint",  "rpot",   "rpen", "rsnd",   "rcolr", "rgr",
    "rjoy",  "rdot"
};


char *petkwcc[] = {	/* PET Basic 4.0 0xcc - 0xda */
    "concat", "dopen", "dclose", "record",
    "header", "collect", "backup", "copy","append", "dsave","dload", "catalog",
    "rename", "scratch", "directory",
    /* Basic 4 Extension for C64 (0xdb - 0xe3) */
    "color", "cold", "key", "dverify", "delete",
    "auto", "merge", "old", "monitor"
};


/* ------------------------------------------------------------------------- */

/*
 * Third party products for VIC-20
 */

/* Turtle Basic v1.0 Keywords  -- Craig Bruce */

char   *turtlekwcc[] = {
    "graphic", "old",  "turn",   "pen",  "draw",  "move", "point", "kill",
    "write", "repeat", "screen", "doke", "reloc", "fill", "rtime", "base",
    "pause", "pop",    "color", "merge", "char",  "take", "sound", "vol",
    "put",   "place",  "cls",  "accept", "reset", "grab", "rdot",  "plr$",
    "deek",  "joy"
};


/*
 * Third party products for C=64
 */


/* Speech Basic v2.7  Keywords (Tokens CC - E6) */

char   *speechkwcc[] = {
    "reset",  "basic", "help",  "key",
    "himem",  "disk",  "dir",   "bload", "bsave",  "map",    "mem", "pause",
    "block",  "hear", "record", "play",  "voldef", "coldef", "hex", "dez",
    "screen", "exec",  "mon",   "<-",    "from",   "speed",  "off"
};


/* @Basic (Atbasic) Keywords (Tokens CC - F6) -- André Fachat */

char   *atbasickwcc[] = {
    "trace", "delete",  "auto", "old", "dump", "find", "renumber", "dload",
    "dsave", "dverify", "directory", "catalog", "scratch", "collect",
    "rename", "copy", "backup", "disk", "header", "append", "merge", "mload",
    "mverify", "msave", "key", "basic", "reset", "exit", "enter", "doke",
    "set", "help", "screen", "lomem", "himem", "colour", "type", "time",
    "deek", "hex$", "bin$", "off", "alarm"
};


/* Simon's Basic Keywords */

char   *simonskw[] = {
    "", "hires", "plot", "line", "block", "fchr", "fcol", "fill",
    "rec", "rot", "draw", "char", "hi col", "inv", "frac",  "move",
    "place", "upb", "upw", "leftw", "leftb", "downb", "downw", "rightb",
    "rightw", "multi", "colour", "mmob", "bflash", "mob set", "music", "flash",
    "repeat", "play", ">>", "centre", "envelope", "cgoto", "wave", "fetch",
    "at(",   "until", ">>", ">>", "use", ">>", "global", ">>",
    "reset", "proc", "call", "exec", "end proc", "exit", "end loop", "on key",
    "disable", "resume", "loop", "delay", ">>",  ">>",  ">>",  ">>",
    "secure", "disapa", "circle", "on error","no error","local","rcomp","else",
    "retrace", "trace", "dir", "page", "dump", "find", "option", "auto",
    "old",  "joy", "mod", "div", ">>", "dup", "inkey", "inst",
    "test", "lin", "exor", "insert", "pot", "penx", ">>", "peny",
    "sound", "graphics", "design", "rlocmob", "cmob", "bckgnds", "pause","nrm",
    "mob off", "off", "angl", "arc", "cold", "scrsv", "scrld", "text",
    "cset",  "vol",  "disk", "hrdcpy", "key", "paint", "low col", "copy",
    "merge", "renumber", "mem", "detect", "check", "display", "err", "out"
};


/* ------------------------------------------------------------------------- */


/*
 * Alternate mnemonics for control codes
 * These are used by MikroBITTI for clarification
 */

char   *a_ctrl1[] = {
    "", "", "", "", "", "wht", "", "",
    "up/lo lock on", "up/lo lock off", "", "", "", "return", "lower case", "",
    "", "down", "rvs on", "home", "delete", "", "", "",
    "",  "",  "",  "esc", "red", "right", "grn", "blu"
};

char   *a_ctrl2[] = {
    "", "orange", "", "", "", "f1", "f3", "f5",
    "f7", "f2", "f4", "f6", "f8", "shift return", "upper case", "",
    "blk",  "up", "rvs off", "clr", "insert", "brown", "lt red", "grey1",
    "grey2", "lt green", "lt blue", "grey3", "pur", "left", "yel", "cyn"
};


/* ------------------------------------------------------------------------- */

static FILE    *source, *dest;
static int     kwlen;

int    main(argc, argv)
    int     argc;
    char   *argv[];
{
    char   *progname, *outfilename = NULL;
    char    realname[24];
    int     reclen;
    int     c;

    long    offset = 0;
    int     wr_mode = 0, version = B_7;		/* best defaults */
    int     load_addr = 0, ctrls= -1, hdr = 1, show_words = 0;
    int     fil = 0, outf = 0, overwrt = 0, textmode = 0;
    int     flg = 0;				/* files on stdin */


    /* Parse arguments */

    progname = argv[0];
    while (--argc && ((*++argv)[0] == '-')) {

	if (!strcmp(argv[0], "--")) {
	    --argc; ++argv;
	    break;
	}

	if (!strcmp(argv[0], "-l")) {		/* load address */
	    if (argc > 1 && sscanf(argv[1], "%x", &load_addr) == 1) {
		--argc; ++argv;
	    continue;
	    }
	    /* Fall to error */
	}

	if (!strcmp(argv[0], "-c")) {
	    ctrls = 1;
	    continue;
	}
	else if (!strcmp(argv[0], "-nc")) {
	    ctrls = 0;
	    continue;
	}

	if (!strcmp(argv[0], "-h")) {
	    hdr = 1;
	    continue;
	}
	else if (!strcmp(argv[0], "-nh")) {
	    hdr = 0;
	    continue;
	}

	else if (!strcmp(argv[0], "-f")) {	/* force overwrite */
	    ++overwrt;
	    continue;
	}

	else if (!strcmp(argv[0], "-o")) {
	    if (argc > 1) {
		outfilename = argv[1];
		++outf;
		--argc; ++argv;
	    continue;
	    }
	    fprintf (stderr, "\nOutput filename missing\n");
	    /* Fall to error */
	}

	/* reading offset */
	if (!strcmp(argv[0], "-skip") || !strcmp(argv[0], "-offset")) {
	    if (argc > 1 && sscanf(argv[1], "%lx", &offset) == 1)
		{
		    --argc; ++argv;
		    continue;
		}
	    /* Fall to error */
	}

	else if (!strcmp(argv[0], "-text")) {	/* force text mode */
	    ++textmode;
	    continue;
	}

	else if (!strcmp(argv[0], "-help") ||
		 !strncmp(argv[0], "-v", 2)) {	/* version ID */
	    fprintf(stderr,
		    "\n\t%s V%4.2f PL %d -- Commodore Basic list/crunch utility.\n",
		    progname, (float)PETCATVERSION, PETCATLEVEL );

	    /* Fall to error for Usage */
	}


	/* Basic version */

	else if (!strncmp(argv[0], "-w", 2) && !wr_mode) {
	    version = parse_version((strlen(argv[0]) > 2 ? &argv[0][2] : NULL));
	    ++wr_mode;
	    continue;
	}

	else if (!strncmp(argv[0], "-k", 2) && !wr_mode) {
	    version = parse_version((strlen(argv[0]) > 2 ? &argv[0][2] : NULL));
	    ++show_words;
	    continue;
	}

	else if ((version = parse_version(&argv[0][1])) >= 0) {
	    continue;
	}

	fprintf(stderr,
		"\nUsage: %7s  [-c | -nc]  [-text | -<version> | -w<version>]\
\n\t\t[-skip bytes] [-l hex]  [--] [file list]\n\t\t[-k[<version>]]\n",
		progname);

	fprintf(stderr, "\n\
   -c\t\tcontrols (interpret also control codes)\n\
   -nc\t\tno controls (suppress control codes in printout)\n\
   -skip <n>\tSkip <n> bytes in the beginning of input file. Ignored on P00.\n\
   -<version>\tuse keywords for <version> instead of the v7.0 ones\n\
   -w<version>\t\ttokenize using keywords on specified Basic version.\n\
   -k<version>\tlist all keywords for the specified Basic version\n\
\t\t  without <version>, list all Basic versions available.\n\
   -l\t\tSpecify load address for program.\n");

	fprintf(stderr, "\n\tVersions:\n\
\t1\t\tPET Basic V1.0\n\
\t2\t\tBasic v2.0\n\
\tsuper\tv2 with Super Expander (VIC)\n\
\tturtle\tv2 with Turtle Basic by Craig Bruce (VIC)\n\
\ta\tv2 with AtBasic (C64)\n\
\tsimon\tlike previous but using Simon's Basic extension (C64)\n\
\tspeech\tlike previous but using Speech Basic v2.7 (C64)\n\
\t4 -w4e\tPET Basic v4.0 program (PET/C64)\n\
\t3\t\tBasic v3.5 program (C16)\n\
\t7\t\tBasic v7.0 program (C128)\n\
\t10\t\tBasic v10.0 program (C64DX)\n\n");

	exit(1);
    }


/*
 * Check parameters
 */

    if (argc)
	fil++;

    if (outf)
	hdr = 0;

    if (version == B_10) {
	keyword[0x63] = "paste";
	keyword[0x64] = "cut";
	keyword[0x65] = "line";
	keyword[0x6e] = "dir";
	kwfe[0x1f] = "dma";
	kwfe[0x21] = "dma";
	kwfe[0x23] = "dma";
    }

    if (show_words) {
	list_keywords(version);
	return (0);
    }

    if (ctrls < 0)
	ctrls = (textmode ? 0 : 1);	/*default ON for prgs, OFF for text */


    if (!load_addr) {
	switch (version) {
	  case B_SUPER:
	    load_addr = 0x0401; break;
	  case B_TURTLE:
	    load_addr = 0x3701; break;
	  case B_35:
	    load_addr = 0x1001; break;
	  case B_7:
	    load_addr = 0x1c01; break;
	  case B_10:
	    load_addr = 0x2001; break;
	  default:
	    load_addr = 0x0801;
	}
    }

    if (wr_mode) {
	fprintf (stderr, "\nLoad address %04x\n", load_addr);
	if ((load_addr & 255) != 1) {
	    fprintf (stderr, "I'm afraid I cannot accept that.\n");
	    exit(1);
	}

	fprintf (stderr, "Control code set: %s\n\n",
		 (ctrls ? "enabled" : "disabled"));
    }


    /*
     * Loop all files
     */

    do {
	int  plen = 0;
	flg = 0;	/* stdin loop flag */


	/*
	 * Try to figure out whether input file is in P00 format or not.
	 * If the header is found, the real filaname is feches and any other
	 * offset specified is overruled.
	 * This is particularly difficult on <stdin>, as only _one_ character
	 * of pushback is guaranteed on all cases. So, the lost bytes "C64File"
	 * are recreated from the magic header while printing the (text) file.
	 */

	if (!fil) {
	    const unsigned char *p;

	    source = stdin;

	    for (plen = 0, p = MagicHeaderP00; plen < 8 &&
		 (c = getc(source)) != EOF && (unsigned)c == *p; ++plen, ++p);

	    if (plen == 8) {
		/* skip the rest of header */
		for (plen = 18; plen > 0 && getc(source) != EOF; --plen);
	    }
	    else {
		/*printf("P00 failed at location %d.\n", plen);*/
		ungetc(c, source);
	    }
	}

	else {
	    if ((source = fopen(argv[0], "rb")) == NULL) {
		fprintf(stderr,
			"\n%s: Can't open file %s\n", progname, argv[0]);
		exit(1);
	    }

	    if (read_pc64header(source, realname, &reclen) == 0) {
		printf ("p00 file.\n");

		/*outfilename = realname;*/
	    }
	    else {
		fseek(source, offset, SEEK_SET); /* rewind or skip beginning */
	    }
	}



	if (!outf)
	    dest = stdout;
	else {
	    /*if (extension && *extension == '.') {
	        sprintf (outfilename, "%s%s", argv[0], extension);
	    }*/

	    if ((dest = fopen(outfilename, "wb")) == NULL) {
		fprintf(stderr,
			"\n%s: Can't open output file %s\n", progname, outfilename);
		exit(1);
	    }
	}


	if (wr_mode) {

	    /* Write P00 header ?  -- Available on tokenizer only */

	    if (is_pc64name(outfilename) >= 0) {
		printf("writing PC64 header.\n");
		write_pc64header(dest, argv[0], 0);
		/*write_pc64header(dest, pc_get_cbmname(argv[0]), 0);*/
	    }

	    p_tokenize(version, load_addr, ctrls);
	}
	else {
	    if (hdr)
		printf("\n\n%s ", (fil ? argv[0] : "<stdin>"));

	    /*
	     * Use TEXT mode if the offset doesn't equal BASIC load addresses
	     * and the first bytes to be read do not contain load address.
	     * Explicitly selected textmode overrules these conditions.
	     */

	    if (textmode || ((offset & 255) !=1 &&
		((c = getc(source)) != EOF && ungetc(c, source) != EOF &&
		c && c != 1)) ) {

		/* Print the bytes lost in header check */
		if (plen > 0 && plen < 8)
		    for (c = 0; c < plen; ++c)
		    fputc (MagicHeaderP00[(int)c], dest);

		pet_2_asc(ctrls);
	    }
	    else {

		/* get load address */
		load_addr =(getc(source) & 0xff) + ((getc(source) & 0xff)<< 8);
		fprintf(dest, "==%04x==\n", load_addr);

		if (p_expand(version, load_addr, ctrls)) {
		    printf ("\n*** Machine language part skipped. ***\n");
		}
		else	/* End of BASIC on stdin. Is there more ? */
		    if (!fil && (c = getc(source)) != EOF &&
			ungetc(c, source) != EOF && c) {
			++flg;
			++hdr;
		    }
	    }

	    if (hdr)
		fputc('\n', dest);
	}


	if (fil)
	    fclose(source);
	if (outf)
	    fclose(dest);

    } while (flg || (fil && --argc && ++argv));		/* next file */
    return(0);
}


/* ------------------------------------------------------------------------- */


/* Parse given version name and return its code, or -1 if not recognized. */

static int    parse_version(str)
    char   *str;
{
    int version = -1;


    if (str == NULL || !*str)
	return 0;

    switch (toupper(*str)) {
      case '1':		/* Basic 1.0 and Basic 10.0 */
	if (str[1] == '0')
	    version = B_10;
	else if (!str[1])
	    version = B_1;
	break;

      case '2':
	version = B_2;
	break;

      case 'A':
	version = B_ATBAS;
	break;

      case 'S':
	switch (str[1]) {
	  case 'u':
	  case 'e':
	    version = B_SUPER;
	    break;
	  case 'i':
	    version = B_SIMON;
	    break;
	  case 'p':
	    version = B_SPEECH;
	    break;
	  default:
	    fprintf (stderr,
		"Please, select one of the following: super, simon, speech\n");
	}
	break;

      case 'T':
	version = B_TURTLE;
	break;

      case '4':
	version = ((str[1]=='e') ? B_4E : B_4);	/* Basic 4.0 */
	break;

      case '3':
	version = B_35;	/* 3.5 */
	break;
      case '7':
	version = B_7;
	break;

      default:
	fprintf (stderr, "\nUnimplemented version '%s'\n", str);
	version = -1;
    }

    return (version);
}


static void   list_keywords(version)
     int version;
{
    int   n, max;

    if (version <= 0 || version > NUM_VERSIONS) {
	printf ("\n  The following versions are supported on  %s V%4.2f\n\n",
		"petcat", (float)PETCATVERSION );

	for (n = 0; n < NUM_VERSIONS; n++)
	    printf ("\t%s\n", VersNames[n]);
	printf ("\n");
	return;
    }


    printf ("\n  Available Keywords on %s\n\n", VersNames[version - 1]);

    if (version == B_1)
	max = NUM_B_1;
    else if (version==B_35 || version==B_7 || version == B_10)
	max = 0x7E;
    else
	max = NUM_COMM;

    for (n = 0; n < max; n++) {
	if (version == B_35 || n != 0x4e)	/* Skip prefix marker */
	    printf("%s\t", keyword[n] /*, n | 0x80*/);
    }
    printf("%s\n", keyword[127]);


    if (version == B_7 || version == B_10) {
	for (n = 2; n < ((version == B_10) ? NUM_V10FE : NUM_V7FE); n++)
	    printf("%s\t", kwfe[n] /*, 0xfe, n*/);

	for (n = 2; n < NUM_KWCE; n++)
	    printf("%s\t", kwce[n] /*, 0xce, n*/);
    }

    else
      switch (version) {
      case B_SUPER:
	for (n = 0; n < NUM_SECC; n++)
	    printf("%s\t", superkwcc[n] /*, n + 0xcc*/);
	break;

      case B_TURTLE:
	for (n = 0; n < NUM_TUCC; n++)
	    printf("%s\t", turtlekwcc[n] /*, n + 0xcc*/);
	break;

	case B_4:
	case B_4E:
	for (n = 0; n < ((version == B_4) ? NUM_V4CC : NUM_4ECC); n++)
	    printf("%s\t", petkwcc[n] /*, n + 0xcc*/);
	break;

	case B_SIMON:
	for (n = 1; n < 0x80; n++)
	    printf("%s\t", simonskw[n] /*, 0x64, n*/);
	break;

      case B_SPEECH:
	for (n = 0; n < NUM_SPECC; n++)
	    printf("%s\t", speechkwcc[n] /*, n + 0xcc*/);
	break;

      case B_ATBAS:
	for (n = 0; n < NUM_ATBCC; n++)
	    printf("%s\t", atbasickwcc[n] /*, n + 0xcc*/);
    }  /* switch */

    printf("\n\n");
}


/* ------------------------------------------------------------------------- */

/*
 * Conversion Routines
 */

static void   pet_2_asc (ctrls)
     int ctrls;
{
    int c;


    while ((c = getc(source)) != EOF) {
	_p_toascii(c, ctrls);		/* convert character */
    }      /* line */
}


static void   _p_toascii(c, ctrls)
     int c;
     int ctrls;
{
    switch (c) {
    case 0x00:				/* 00 for SEQ */
    case 0x0a:
    case 0x0d:
      fputc ('\n', dest);
      break;
    case 0x60:
      fprintf(dest, "(SHIFT-*)");
      break;
    case 0x7c:
      fprintf(dest, "(CBM--)");		/* Conflicts with Scandinavian Chars */
      break;
    case 0x7f:
      fprintf(dest, "(CBM-*)");
      break;
    case 0xa0:				/* CBM: Shifted Space */
    case 0xe0:
      if (!ctrls)
	  fputc (' ', dest);
      else
	  fprintf(dest, "($%02x)", c & 0xff);
      break;
    case 0xff:
      fputc (0x7e, dest);
      break;

    default:
      switch (c & 0xe0) {
      case 0x40:		/* 41 - 7F */
      case 0x60:
	fputc (c ^ 0x20, dest);
	break;
      case 0xa0:		/* A1 - BF */
      case 0xe0:		/* E1 - FE */
	fprintf(dest, "(%s)", cbmkeys[c & 0x1f]);
	break;
      case 0xc0:		/* C0 - DF */
	fputc (c ^ 0x80, dest);
	break;

      default:
	if (isprint(c))
	   fputc (c, dest);

	else if (ctrls) {
	  if ((c < 0x20) && *ctrl1[c])
	     fprintf(dest, "(%s)", ctrl1[c]);
	  else if ((c > 0x7f) && (c < 0xa0) && *ctrl2[c & 0x1f])
	     fprintf(dest, "(%s)", ctrl2[c & 0x1f]);
	  else
	     fprintf(dest, "($%02x)", c & 0xff);
	}  /* ctrls */
      }  /* switch */
    }  /* switch */
}


/*
 * This routine starts from the beginning of Basic, and not from the
 * load address included on program files. That way it can list from
 * RAM dump.
 */

static int   p_expand(version, addr, ctrls)
     int addr, ctrls, version;
{
    static char line[4];
    unsigned int c;
    int quote, spnum, directory = 0;
    int sysflg = 0;


    /*
     * It seems to be common mistake not to terminate BASIC properly
     * before the machine language part, so we don't check for the
     * low byte of line link here.
     * Line link and line number are read separately to leave possible
     * next file on stdin intact.
     */

    while ((fread(line, 1, 2, source) == 2) && (/*line[0] ||*/ line[1]) &&
	   fread(line + 2, 1, 2, source) == 2) {
	quote = 0;
	fprintf(dest, " %4d ",
		(spnum = (line[2] & 0xff) + ((line[3] & 0xff) << 8)) );

	if (directory) {
	    if (spnum >= 100)
		spnum = 0;
	    else if (spnum >= 10)
		spnum = 1;
	    else
		spnum = 2;
	}

	/* prevent list protection from terminating listing */

	while ((c = getc(source)) != EOF && !c);

	if (c == 0x12 && !line[2] && !line[3]) {  /* 00 00 12 22 */
	    directory++;
	}

	do {
	    if (c == 0x22)
		quote ^= c;

	    /*
	     * Simon's basic. Any flag for this is not needed since it is
	     * mutually exclusive with all other implemented modes.
	     */

	    if (!quote && (c == 0x64)) {
		if ((c = getc(source)) < 0x80) {
		    fprintf(dest, "%s", simonskw[c]);
		    continue;
		} else
		    fprintf(dest, "($64)");	/* it wasn't prefix */
	    }

	    /* basic 2.0, 7.0 & 10.0 and extensions */

	    if (!quote && c > 0x7f) {
		if (c <= MAX_COMM) {
		    fprintf(dest, "%s", keyword[c & 0x7f]);

		    if (c == 0x9E) {
			++sysflg;
		    }
		    continue;
		}
		if (version != B_35) {
		    if (c == 0xce) {		/* 'rlum' on V3.5*/
			if ((c = getc(source)) <= MAX_KWCE)
			    fprintf(dest,"%s", kwce[c]);
			else
			    fprintf(dest, "($ce%02x)", c);
			continue;
		    } else if (c == 0xfe) {
			if ((c = getc(source)) <= MAX_V10FE)
			    fprintf(dest, "%s", kwfe[c]);
			else
			    fprintf(dest, "($fe%02x)", c);
			continue;
		    }
		}

		switch (version) {
		  case B_2:
		  case B_SUPER:		/* VIC extension */
		    if (c >= 0xcc && c <= MAX_SECC)
			fprintf(dest, "%s", superkwcc[c - 0xcc]);
		    break;

		  case B_TURTLE:	/* VIC extension as well */
		    if (c >= 0xcc && c <= MAX_TUCC)
			fprintf(dest, "%s", turtlekwcc[c - 0xcc]);
		    break;

		  case B_SPEECH:	/* C64 Speech basic */
		    if (c >= 0xcc && c <= MAX_SPECC)
			fprintf(dest, "%s", speechkwcc[c - 0xcc]);
		    break;

		  case B_ATBAS:		/* C64 Atbasic */
		    if (c >= 0xcc && c <= MAX_ATBCC)
			fprintf(dest, "%s", atbasickwcc[c - 0xcc]);
		    break;

		  case B_4:		/* PET V4.0 */
		  case B_4E:	/* V4.0 extension */
		    if (c >= 0xcc && c <= MAX_4ECC)
			fprintf(dest, "%s", petkwcc[c - 0xcc]);
		    break;

		  default:		/* C128 */
		    fprintf(dest, "%s", keyword[c & 0x7f]);
		}
		continue;
	    } /* quote */

	    if (directory && spnum && c == 0x20) {
		spnum--;	  /* eat spaces to adjust directory lines */
		continue;
	    }

	    _p_toascii(c, ctrls);	/* convert character */

	} while ((c = getc(source)) != EOF && c);
	printf("\n");

    }      /* line */

#ifdef DEBUG
    printf("\n c %02x  EOF %d  *line %d  sysflg %d\n",
	   c, feof(source), *line, sysflg);
#endif

    return (!feof(source) && (*line | line[1]) && sysflg);
}


static void   p_tokenize(version, addr, ctrls)
     int version;
     int addr;
     int ctrls;
{
    static char    line[256];
    unsigned char *p1, *p2, quote, c;
    int            len = 0, linum = 10, match;

    fprintf(dest, "%c%c", (addr & 255), ((addr >> 8) & 255) );


    /* Copies from p2 to p1 */

    while((p1 = p2 = (unsigned char *)fgets(line, 255, source)) != NULL) {

#ifndef GEMDOS
	if (sscanf(line, "%d%n", &linum, &len) == 1)
	    p2 += len;
#else
	if (sscanf(line, "%d", &linum) == 1)
	    while (isspace(*p2) || isdigit(*p2)) p2++;
#endif
	quote = 0;
	while (isspace(*p2)) p2++;

	while (*p2) {
	    if (*p2 == 0x22) {
		quote ^= *p2;
	    }
	    if (*p2 == 0x0a || *p2 == 0x0d)
		break;

	    match = 0;
	    if (quote) {
		/*
		 * control code evaluation
		 * only strings that appear inside quotes are
		 * interpreted -- they should not be used elsewhere
		 */

		if (ctrls && (*p2 == CLARIF_LP)) {
		    unsigned char *p;
		    p = p2;

		    /* repetition count */
		    len = 1;
#ifndef GEMDOS
		    if (sscanf((char *)++p, "%d%n", &len, &kwlen) == 1) {
			p += kwlen;
#else
		    if (sscanf(++p, "%d", &len) == 1) {
			while (isspace(*p) || isdigit(*p)) p++;
#endif
			if (*p == ' ')
			    ++p;
		    }
		    /*fprintf(stderr, "count %d\n", len);*/

		    if (( ((c = sstrcmp(p, ctrl1, 0, 0x20)) != KW_NONE) ||
			 ((c = sstrcmp(p, a_ctrl1, 0, 0x20)) !=KW_NONE) ||

			((((c = sstrcmp(p, ctrl2, 0, 0x20)) != KW_NONE) ||
			 ((c = sstrcmp(p, a_ctrl2, 0, 0x20)) !=KW_NONE)) &&
			 (c |= 0x80)) ||

			( ((c = sstrcmp(p, cbmkeys, 0, 0x20)) != KW_NONE) &&
			 (c |= 0xA0)) ) &&	/* CBM-x images */

			p[kwlen] == CLARIF_RP) {

			for (; len-- > 0;)
			    *p1++ = c;
			p2 = p + (++kwlen);
			continue;
		    }
		}
	    }
	    else if (isalpha(*p2) || strchr("+-*/^>=<", *p2)) {

		/* FE and CE prefixes are checked first */
		if (version == B_7 || version == B_10) {
		    if ((c = sstrcmp(p2, kwfe, 2,
			((version == B_10) ? NUM_V10FE : NUM_V7FE))) != KW_NONE) {
			*p1++ = 0xfe;
			*p1++ = c;
			p2 += kwlen;
			match++;
		    }
		    else if ((c = sstrcmp(p2, kwce, 2, NUM_KWCE)) != KW_NONE) {
			*p1++ = 0xce;
			*p1++ = c;
			p2 += kwlen;
			match++;
		    }
		}

		/* Common Keywords
		 * Note:  ~ (pi) is tested later */


		if (!match) {
		    int max;

		    if (version == B_1)
			max = NUM_B_1;
		    else if (version==B_35 || version== B_7 || version == B_10)
			max = 0x7E;
		    else
			max = NUM_COMM;

		    if ((c = sstrcmp(p2, keyword, 0, max)) != KW_NONE) {

			if (version == B_35 || c != 0x4e) {  /* Skip prefix */
			    *p1++ = c | 0x80;
			    p2 += kwlen;
			    match++;
			}
		    }
		}

		if (!match)
		  switch (version) {
		  case B_SUPER:
		    if ((c = sstrcmp(p2, superkwcc, 0, NUM_SECC)) != KW_NONE) {
			*p1++ = c + 0xcc;
			p2 += kwlen;
			match++;
		    }
		    break;

		  case B_TURTLE:
		    if ((c = sstrcmp(p2, turtlekwcc, 0, NUM_TUCC)) !=KW_NONE) {
			*p1++ = c + 0xcc;
			p2 += kwlen;
			match++;
		    }
		    break;

		  case B_4:
		  case B_4E:
		    if ((c = sstrcmp(p2, petkwcc, 0,
			((version == B_4) ? NUM_V4CC : NUM_4ECC))) != KW_NONE) {
			*p1++ = c + 0xcc;
			p2 += kwlen;
			match++;
		    }
		    break;

		  case B_SIMON:
		    if ((c = sstrcmp(p2, simonskw, 1, 0x80)) != KW_NONE) {
			*p1++ = 0x64;
			*p1++ = c;
			p2 += kwlen;
			match++;
		    }
		    break;

		  case B_SPEECH:
		    if ((c = sstrcmp(p2, speechkwcc, 0, NUM_SPECC)) !=KW_NONE) {
			*p1++ = c + 0xcc;
			p2 += kwlen;
			match++;
		    }
		    break;

		  case B_ATBAS:
		    if ((c = sstrcmp(p2, atbasickwcc, 0, NUM_ATBCC)) !=KW_NONE) {
			*p1++ = c + 0xcc;
			p2 += kwlen;
			match++;
		    }
		}  /* switch */
	    } /* !quote */

	    if (!match) {
		if (*p2 == 0x7e)		/*  '~' is ASCII for 'pi' */
		    *p1++ = 0xff;

		else if ((*p2 >= 0x5b) && (*p2 <= 0x7e))
		    *p1++ = *p2 ^ 0x20;

		else if ((*p2 >= 'A') && (*p2 <= 'Z'))
		    *p1++ = *p2 | 0x80;

		else
		    *p1++ = *p2;

		++p2;
	    } /* match */
	} /* while */

	*p1 = 0;
	if ((len = strlen(line) ) > 0) {
	    addr += len + 5;
	    fprintf(dest, "%c%c%c%c%s%c", addr & 255, (addr>>8) & 255,
		   linum & 255, (linum>>8) & 255, line, '\0');

	    linum += 2; /* auto line numbering by default */
	}
    } /* while */

    fprintf(dest, "%c%c", 0, 0);	/* program end marker */
}


static unsigned char sstrcmp(line, wordlist, token, maxitems)
     unsigned char *line;
     char **wordlist;
     int    token, maxitems;
{
    int     j;
    char   *p, *q;

    kwlen = 1;
    /* search for keyword */
    for (; token < maxitems; token++) {
	for (p = wordlist[token], q = (char *)line, j = 0;
	     *p && *q && *p == *q; p++, q++, j++);

	/*fprintf (stderr,
		 "compare %s %s - %d %d\n", wordlist[token], line, j, kwlen);*/

	/* found an exact or abbreviated keyword
	 */
	if (j && (!*p || (*p && (*p ^ *q) == 0x20 && j++)) ) {
	    kwlen = j;
	    /*fprintf (stderr, "found %s %2x\n", wordlist[token], token);*/
	    return token;
	}
    } /* for */

    return (KW_NONE);
}

These are the contents of the former NiCE NeXT User Group NeXTSTEP/OpenStep software archive, currently hosted by Netfuture.ch.