aboutsummaryrefslogtreecommitdiff
path: root/unmac65.c
diff options
context:
space:
mode:
authorB. Watson <urchlay@slackware.uk>2022-08-29 16:11:13 -0400
committerB. Watson <urchlay@slackware.uk>2022-08-29 16:11:13 -0400
commite2ba8458a5cfdfacfaf103e7ba97d610afa6c970 (patch)
treecd665e602e6e2b636578a7d3d7894380605dafcc /unmac65.c
downloadbw-atari8-tools-e2ba8458a5cfdfacfaf103e7ba97d610afa6c970.tar.gz
initial commit
Diffstat (limited to 'unmac65.c')
-rw-r--r--unmac65.c1041
1 files changed, 1041 insertions, 0 deletions
diff --git a/unmac65.c b/unmac65.c
new file mode 100644
index 0000000..42875bb
--- /dev/null
+++ b/unmac65.c
@@ -0,0 +1,1041 @@
+#include <stdio.h>
+#include <unistd.h>
+#include <stdlib.h>
+#include <string.h>
+#include <ctype.h>
+
+#ifndef VERSION
+# define VERSION "???"
+#endif
+
+#ifndef TAG
+# define TAG ""
+#endif
+
+#define SELF "unmac65"
+#define BANNER SELF " v" VERSION " by B. Watson (WTFPL)\n\n"
+
+#ifdef __CC65__
+# ifdef __ATARI__
+# define ATARI8
+# else
+# error "This program only supports Atari 8-bit when built with cc65"
+/* Feel free to add support for other systems, if you need to. The main
+ differences will be in fix_filename() and atari8_get_opts(). */
+# endif
+#else
+# undef ATARI8
+#endif
+
+#define lsbmsb(lo, hi) ( (lo) | (hi << 8) )
+
+/* use static filename buffers on A8, since we (probably) won't be
+ getting them passed with argv. FIXME: I really ought to do bounds
+ checking... */
+#ifdef ATARI8
+char infile[128], outfile[128];
+#else
+char *infile = NULL, *outfile = NULL;
+#endif
+
+FILE *input = NULL, *output = NULL;
+int using_stdout = 1;
+
+/* see handle_cli_opts() and/or atari8_get_opts() for these: */
+char nl = '\n';
+char no_numbers = 0;
+char leading_tabs = 0;
+char all_tabs = 0;
+char omit_dots = 0;
+char lcase_opcodes = 0;
+char lcase_all = 0;
+char dump_tokens = 0;
+char add_quote = 0;
+
+unsigned int renum_start, renum_line;
+int renum_incr = 0;
+
+typedef enum { CC_NONE, CC_UNPRINT, CC_ALL } chconst_opt_t;
+chconst_opt_t chconsts_hex = 0;
+
+/* options only available in the non-Atari8 ports */
+#ifndef ATARI8
+char deinverse = 0;
+char found_inverse = 0;
+char found_unprint = 0;
+char inv_underscore = 0;
+char inv_ansi = 0;
+char unicode = 0;
+#endif
+
+/* dumpbuf[] really should be local to parse_one_line(), but
+ cc65 won't let us make this a local var, it's too big. 1000 bytes
+ is plenty (max line length is 256 bytes, we dump them in hex at
+ 3 chars each, plus 10-12 chars worth of formatting) */
+char dumpbuf[1000];
+
+/* number of bytes left to read (initialized from 4-byte m65 header,
+ decremented by next_byte()). If this ever reaches 0 while there's
+ more input, or if we get EOF while prog_bytes != 0, it's an error. */
+unsigned int prog_bytes;
+
+/* Guess what these are for? */
+int line_number, old_line_number = -1;
+
+char *opcode_tokens[] = {
+ "ERROR -", /* 0x00 */
+ ".IF",
+ ".ELSE",
+ ".ENDIF",
+ ".MACRO",
+ ".ENDM",
+ ".TITLE",
+ " ",
+ ".PAGE",
+ ".WORD",
+ ".ERROR",
+ ".BYTE",
+ ".SBYTE",
+ ".DBYTE",
+ ".END",
+ ".OPT",
+ ".TAB", /* 0x10 */
+ ".INCLUDE",
+ ".DS",
+ ".ORG",
+ ".EQU",
+ "BRA",
+ "TRB",
+ "TSB",
+ ".FLOAT",
+ ".CBYTE",
+ ";",
+ ".LOCAL",
+ ".SET",
+ "*=",
+ "=",
+ ".=",
+ "JSR", /* 0x20 */
+ "JMP",
+ "DEC",
+ "INC",
+ "LDX",
+ "LDY",
+ "STX",
+ "STY",
+ "CPX",
+ "CPY",
+ "BIT",
+ "BRK",
+ "CLC",
+ "CLD",
+ "CLI",
+ "CLV",
+ "DEX", /* 0x30 */
+ "DEY",
+ "INX",
+ "INY",
+ "NOP",
+ "PHA",
+ "PHP",
+ "PLA",
+ "PLP",
+ "RTI",
+ "RTS",
+ "SEC",
+ "SED",
+ "SEI",
+ "TAX",
+ "TAY",
+ "TSX", /* 0x40 */
+ "TXA",
+ "TXS",
+ "TYA",
+ "BCC",
+ "BCS",
+ "BEQ",
+ "BMI",
+ "BNE",
+ "BPL",
+ "BVC",
+ "BVS",
+ "ORA",
+ "AND",
+ "EOR",
+ "ADC",
+ "STA", /* 0x50 */
+ "LDA",
+ "CMP",
+ "SBC",
+ "ASL",
+ "ROL",
+ "LSR",
+ "ROR",
+ "", /* 0x58 - the null opcode */
+ "STZ",
+ "DEA",
+ "INA",
+ "PHX",
+ "PHY",
+ "PLX",
+ "PLY" /* 0x5f */
+};
+
+/* Special opcodes */
+#define MAX_OPCODE 0x5f
+#define NO_OPCODE 0x58
+
+char *operand_tokens[] = {
+ NULL,
+ NULL,
+ NULL,
+ NULL,
+ NULL,
+ NULL,
+ NULL,
+ NULL,
+ NULL,
+ NULL,
+ NULL, /* actually "'", special handling tho */ /* 0x0a, 10 decimal */
+ "%$",
+ "%",
+ "*",
+ " ",
+ " ",
+ "a", /* 0x10 */
+ "q",
+ "+",
+ "-",
+ "*", /* 0x14, 20 decimal */
+ "/",
+ "&",
+ ".DEF",
+ "=",
+ "<=",
+ ">=",
+ "<>",
+ ">",
+ "<",
+ "-", /* 0x1e, 30 dec */
+ "[",
+ "]", /* 0x20 */
+ ".OR",
+ ".AND",
+ ".NOT",
+ "!",
+ "^",
+ ".REF",
+ "\\",
+ NULL, /* 0x28, 40 dec */
+ NULL,
+ NULL,
+ NULL,
+ NULL,
+ NULL,
+ NULL,
+ ".REF",
+ ".DEF ", /* 0x30 */
+ ".NOT ",
+ " .AND ", /* 0x32, 50 dec */
+ " .OR ",
+ " <",
+ " >",
+ ",X)",
+ "),Y",
+ ",Y",
+ ",X",
+ ")",
+ ",", /* 0x3b, 59, the null operand */
+ "\x1b", /* 0x3c, 60, ASCII escape, chr$(27)? */
+ ",",
+ "#",
+ "A",
+ "(", /* 0x40, 64 dec */
+ "\"",
+ "$",
+ "Q",
+ "NO",
+ "NO ",
+ "OBJ", /* 0x46, 70 dec */
+ "ERR",
+ "EJECT",
+ "LIST",
+ "XREF",
+ "MLIST",
+ "CLIST",
+ "NUM", /* 0x4d, 77 dec */
+ /* "M", */ /* maybe? I think not... */
+};
+
+/* Special operands */
+#define MAX_OPERAND 0x4d
+#define NO_OPERAND 0x3b
+
+#define HEXWORD_PREFIX 5
+#define HEXBYTE_PREFIX 6
+
+#define DECWORD_PREFIX 7
+#define DECBYTE_PREFIX 8
+
+#define CHAR_CONST_PREFIX 0x0a
+
+/* Functions */
+void print_label_byte(unsigned char byte, FILE *output) {
+ putc((lcase_all ? tolower(byte) : byte), output);
+}
+
+#ifdef ATARI8
+void print_string_byte(unsigned char byte, FILE *output) {
+ putc(byte, output);
+}
+#else
+char *unicode_table[] = {
+ "♥", "┣", "┃", "┛", "┫", "┓", "╱", "╲", "◢", "▗", "◣", "▝", "▘", "▔", "▁", "▖",
+ "♣", "┏", "━", "╋", "⚫ ", "▄", "▎", "┳", "┻", "▌", "┗", "␛", "↑", "↓", "←", "→",
+ " ", "!", "\"", "#", "$", "%", "&", "'", "(", ")", "*", "+", ",", "-", ".", "/",
+ "0", "1", "2", "3", "4", "5", "6", "7", "8", "9", ":", ";", "<", "=", ">", "?",
+ "@", "A", "B", "C", "D", "E", "F", "G", "H", "I", "J", "K", "L", "M", "N", "O",
+ "P", "Q", "R", "S", "T", "U", "V", "W", "X", "Y", "Z", "[", "\\", "]", "^", "_",
+ "◆", "a", "b", "c", "d", "e", "f", "g", "h", "i", "j", "k", "l", "m", "n", "o",
+ "p", "q", "r", "s", "t", "u", "v", "w", "x", "y", "z", "♠", "|", "↰", "◀", "▶"
+};
+
+/* This is probably more complex than it needs to be. */
+void start_inverse(FILE *output) {
+ if(inv_underscore) {
+ fputs("_\x08", output);
+ } else if(inv_ansi) {
+ fputs("\x1b[7m", output);
+ }
+}
+
+void end_inverse(FILE *output) {
+ if(inv_underscore) {
+ /* do nothing */;
+ } else if(inv_ansi) {
+ fputs("\x1b[0m", output);
+ }
+}
+
+void print_string_byte(unsigned char byte, FILE *output) {
+ int inverse;
+
+ inverse = (byte >= 0x80);
+ byte &= 0x7f;
+ if(inverse && deinverse) {
+ found_inverse++;
+ inverse = 0;
+ }
+
+ if(inverse) start_inverse(output);
+ if(unicode) {
+ fputs(unicode_table[byte], output);
+ } else {
+ if(inv_underscore || inv_ansi) {
+ putc(byte, output);
+ } else {
+ putc(inverse ? (byte | 0x80) : byte, output);
+ found_inverse += inverse;
+ }
+ if(byte < 0x20) found_unprint++;
+ }
+ if(inverse) end_inverse(output);
+}
+
+#endif
+
+#define print_comment_byte(x, y) print_string_byte((lcase_all ? tolower(x) : x), y)
+
+void print_hex_byte(unsigned char byte, FILE *output) {
+ fprintf(output, (lcase_opcodes ? "$%02x" : "$%02X"), byte);
+}
+
+void print_hex_word(int word, FILE *output) {
+ fprintf(output, (lcase_opcodes ? "$%04x" : "$%04X"), word);
+}
+
+
+void closeall() {
+ if(output && !using_stdout) {
+ fclose(output);
+ output = NULL;
+ }
+
+ if(input) {
+ fclose(input);
+ input = NULL;
+ }
+}
+
+void exit_cleanly(int status) {
+#ifdef ATARI8
+ if(status) {
+ puts("Press Return to exit:");
+ fflush(stdout);
+ fgets(infile, 80, stdin);
+ }
+#endif
+
+ closeall();
+ exit(status);
+}
+
+unsigned char next_byte() {
+ int c;
+
+ if(prog_bytes <= 0) {
+ fprintf(stderr, SELF ": corrupt or truncated file?\n");
+ exit_cleanly(1);
+ }
+
+ --prog_bytes;
+ c = getc(input);
+ if(c == EOF && prog_bytes != 0) {
+ fprintf(stderr, SELF ": unexpected EOF\n");
+ exit_cleanly(1);
+ }
+
+ if(prog_bytes == 0 && getc(input) != EOF) {
+ fprintf(stderr, SELF ": ignoring extra junk at EOF\n");
+ }
+
+ return (unsigned char)c;
+}
+
+void parse_header(void) {
+ unsigned char inbuf[4];
+ int bytes = fread(inbuf, 1, 4, input);
+ if(bytes < 4) {
+ if(ferror(input)) {
+ perror(infile);
+ } else {
+ fprintf(stderr, SELF ": file is too short (%d bytes)\n", bytes);
+ }
+ exit_cleanly(1);
+ }
+
+ if(inbuf[0] != 0xfe || inbuf[1] != 0xfe) {
+ fprintf(stderr, SELF ": not a mac/65 file (missing $FEFE header)\n");
+ exit_cleanly(1);
+ }
+
+ prog_bytes = lsbmsb(inbuf[2], inbuf[3]);
+ if(!prog_bytes) fprintf(stderr, SELF ": file is valid but contains no lines of code\n");
+
+ if(dump_tokens) {
+ fprintf(output, ";; Mac/65 header: ");
+ for(bytes = 0; bytes < 4; bytes++)
+ fprintf(output, "%02X ", inbuf[bytes]);
+ fprintf(output, "length %d\n", prog_bytes);
+ }
+}
+
+void print_opcode(unsigned char byte) {
+ char *opc = opcode_tokens[byte];
+
+ putc((leading_tabs ? '\t' : ' '), output);
+ if(omit_dots && opc[0] == '.' && isalpha(opc[1]))
+ ++opc;
+
+ if(lcase_opcodes) {
+ char buf[10];
+ char *p = buf;
+ strcpy(buf, opc);
+ while(*p) {
+ *p = tolower(*p);
+ ++p;
+ }
+ opc = buf;
+ }
+
+ fputs(opc, output);
+ putc((all_tabs ? '\t' : ' '), output);
+}
+
+/* Parser states. The parser is hand-rolled and kind of ugly. */
+#define ST_NEED_OPCODE 1
+#define ST_IN_LABEL 2
+#define ST_IN_OPERAND 3
+#define ST_IN_OPSTRING 4
+#define ST_IN_COMMENT 5
+#define ST_ERROR 6
+#define ST_IN_HEXBYTE 7
+#define ST_IN_HEXWORD_LSB 8
+#define ST_IN_HEXWORD_MSB 9
+#define ST_IN_DECBYTE 10
+#define ST_IN_DECWORD_LSB 11
+#define ST_IN_DECWORD_MSB 12
+#define ST_IN_CHAR_CONST 13
+
+int handle_operand(unsigned char byte) {
+ char *operand = NULL;
+
+ if(byte > 0x80)
+ return ST_IN_OPSTRING;
+
+ if(byte == NO_OPERAND) {
+ putc((all_tabs ? '\t' : ' '), output);
+ return ST_IN_COMMENT;
+ }
+
+ if(byte > MAX_OPERAND)
+ return ST_ERROR;
+
+ operand = operand_tokens[byte];
+ if(operand) {
+ fputs(operand, output);
+ return ST_IN_OPERAND;
+ }
+
+ switch(byte) {
+ case HEXWORD_PREFIX:
+ return ST_IN_HEXWORD_LSB;
+
+ case HEXBYTE_PREFIX:
+ return ST_IN_HEXBYTE;
+
+ case DECWORD_PREFIX:
+ return ST_IN_DECWORD_LSB;
+
+ case DECBYTE_PREFIX:
+ return ST_IN_DECBYTE;
+
+ case CHAR_CONST_PREFIX:
+ return ST_IN_CHAR_CONST;
+
+ /* TODO: find out if any other specials exist */
+
+ default:
+ fprintf(output, "[$%02X?]", byte);
+ return ST_IN_OPERAND;
+ }
+}
+
+void parse_one_line() {
+ int state = ST_NEED_OPCODE;
+ int string_len = 0;
+ int line_bytes;
+ int lsb = 0;
+ unsigned char byte, linenum_lo, linenum_hi;
+ char hexbuf[20];
+
+#ifndef ATARI8
+ found_inverse = found_unprint = 0;
+#endif
+
+ linenum_lo = next_byte();
+ linenum_hi = next_byte();
+
+ if(renum_incr) {
+ line_number = renum_line;
+ renum_line += renum_incr;
+ } else {
+ line_number = lsbmsb(linenum_lo, linenum_hi);
+ }
+
+ if(line_number <= old_line_number) {
+ fprintf(stderr, SELF ": line #%d <= prev line #%d\n",
+ line_number, old_line_number);
+ }
+
+ line_bytes = next_byte() - 3;
+
+ if(dump_tokens)
+ sprintf(dumpbuf, ";; %d (%02X %02X, len %02X):",
+ line_number, linenum_lo, linenum_hi, line_bytes + 3);
+
+ if(!no_numbers) {
+ char *format = "%06d ";
+
+ /* duplicate mac65's weird line number formatting */
+ if(line_number < 100)
+ format = "%02d ";
+ else if(line_number < 10000)
+ format = "%04d ";
+
+ fprintf(output, format, line_number);
+ }
+
+ while(line_bytes) {
+ byte = next_byte();
+ --line_bytes;
+
+ if(dump_tokens) {
+ sprintf(hexbuf, " %02X", byte);
+ strcat(dumpbuf, hexbuf);
+ }
+
+ switch(state) {
+ case ST_NEED_OPCODE:
+ if(byte > 0x80) {
+ string_len = byte & 0x7f;
+ state = ST_IN_LABEL;
+ } else if(byte == NO_OPCODE) {
+ state = ST_IN_COMMENT;
+ } else if(byte <= MAX_OPCODE) {
+ print_opcode(byte);
+ state = ST_IN_OPERAND;
+ } else {
+ state = ST_ERROR;
+ }
+ break;
+
+ case ST_IN_LABEL:
+ print_label_byte(byte, output);
+ if(--string_len == 0)
+ state = ST_NEED_OPCODE; /* TODO: 2 labels is error, detect */
+ break;
+
+ case ST_IN_OPERAND:
+ state = handle_operand(byte);
+ if(state == ST_IN_OPSTRING) string_len = byte & 0x7f;
+ break;
+
+ case ST_IN_OPSTRING:
+ print_string_byte(byte, output);
+ if(--string_len == 0)
+ state = ST_IN_OPERAND;
+ break;
+
+ case ST_IN_COMMENT:
+ print_comment_byte(byte, output);
+ break;
+
+ case ST_ERROR:
+ fprintf(output, "<$%02X?>", byte);
+ break;
+
+ case ST_IN_HEXBYTE:
+ print_hex_byte(byte, output);
+ state = ST_IN_OPERAND;
+ break;
+
+ case ST_IN_HEXWORD_LSB:
+ lsb = byte;
+ state = ST_IN_HEXWORD_MSB;
+ break;
+
+ case ST_IN_HEXWORD_MSB:
+ print_hex_word(lsbmsb(lsb, byte), output);
+ state = ST_IN_OPERAND;
+ break;
+
+ case ST_IN_DECBYTE:
+ fprintf(output, "%d", byte);
+ state = ST_IN_OPERAND;
+ break;
+
+ case ST_IN_DECWORD_LSB:
+ lsb = byte;
+ state = ST_IN_DECWORD_MSB;
+ break;
+
+ case ST_IN_DECWORD_MSB:
+ fprintf(output, "%d", lsbmsb(lsb, byte));
+ state = ST_IN_OPERAND;
+ break;
+
+ case ST_IN_CHAR_CONST:
+#ifndef ATARI8
+ if(
+ chconsts_hex == CC_ALL ||
+ (chconsts_hex == CC_UNPRINT && (byte < 0x20 || byte > 0x7f))
+ )
+ {
+ print_hex_byte(byte, output);
+ state = ST_IN_OPERAND;
+ break;
+ }
+#endif
+ putc('\'', output);
+ print_string_byte(byte, output);
+ if(add_quote)
+ putc('\'', output);
+ state = ST_IN_OPERAND;
+ break;
+
+ default:
+ fprintf(stderr, SELF ": internal error, state %d\n", state);
+ state = ST_ERROR;
+ break;
+ }
+ }
+
+#ifndef ATARI8
+ if(found_inverse) {
+ fprintf(stderr, SELF ": line %d contains %d inverse ATASCII characters >= $80\n", line_number, found_inverse);
+ if(deinverse) printf("; XXX inverse (%d chars)", found_inverse);
+ }
+ if(found_unprint) fprintf(stderr, SELF ": line %d contains %d non-printable ATASCII characters <= $1F\n", line_number, found_unprint);
+#endif
+
+#ifdef CYGWIN_NEWLINE_HACK
+ if(nl == '\n') putc('\r', output);
+#endif
+ putc(nl, output);
+
+ if(dump_tokens) {
+ fputs(dumpbuf, output);
+#ifdef CYGWIN_NEWLINE_HACK
+ if(nl == '\n') putc('\r', output);
+#endif
+ putc(nl, output);
+ }
+
+ if(ferror(output)) {
+ perror(SELF);
+ exit_cleanly(1);
+ }
+}
+
+void parse_lines() {
+ old_line_number = -1;
+
+ while(prog_bytes)
+ parse_one_line();
+
+ closeall();
+}
+
+#ifdef ATARI8
+/* get rid of trailing newline, make uppercase... */
+void cleanstring(char *s) {
+ while(*s) {
+ *s = toupper(*s);
+ if(*s == '\n') *s = 0;
+ ++s;
+ }
+}
+
+/* add .M65 if no extension entered, add leading D: if
+ no device name. */
+void fix_filename(char *src, char *dst, char *ext) {
+ if(!strrchr(src, '.'))
+ strcat(src, ext);
+
+ dst[0] = '\0';
+
+ if(!strrchr(src, ':'))
+ strcat(dst, "D:");
+
+ strcat(dst, src);
+}
+
+void prompt_for_opt(char *prompt, char *opt) {
+ char buffer[10];
+
+ printf("%s [%c/%c]?",
+ prompt,
+ (*opt ? 'Y' : 'y'),
+ (*opt ? 'n' : 'N'));
+
+ fflush(stdout);
+ fgets(buffer, 10, stdin);
+ cleanstring(buffer);
+
+ if(buffer[0] == 'Y' || buffer[0] == 'y')
+ *opt = 1;
+ else if(buffer[0] == 'N' || buffer[0] == 'n')
+ *opt = 0;
+ /* else leave unchanged */
+}
+
+void prompt_for_str(char *prompt, char *buf) {
+ fputs(prompt, stdout);
+ putc('?', stdout);
+ fflush(stdout);
+ fgets(buf, 120, stdin);
+ cleanstring(buf);
+}
+
+void atari8_get_opts() {
+ char buffer[128];
+ char other = 0;
+
+ prompt_for_str("M65 file or Return to quit", buffer);
+
+ if(!buffer[0]) {
+ prompt_for_opt("Really quit", &other);
+ if(other)
+ exit_cleanly(0);
+ else
+ other = 0;
+ }
+
+ fix_filename(buffer, infile, ".M65");
+
+ prompt_for_str("Output file (Return for E:)", buffer);
+
+ if(!buffer[0]) {
+ using_stdout = 1;
+ } else {
+ using_stdout = 0;
+ fix_filename(buffer, outfile, ".TXT");
+ }
+
+ prompt_for_opt("Set other options", &other);
+ if(other) {
+ prompt_for_opt("Omit line numbers", &no_numbers);
+ if(!no_numbers) {
+ prompt_for_opt("Renumber lines", &other);
+ if(other) {
+ prompt_for_str("Starting line number", buffer);
+ renum_start = atoi(buffer);
+ prompt_for_str("Line num increment (Return = 10)", buffer);
+ renum_incr = atoi(buffer);
+ if(renum_incr < 1) renum_incr = 10;
+ } else {
+ renum_incr = 0;
+ }
+ }
+ prompt_for_opt("Omit . (dots) from pseudo-ops", &omit_dots);
+
+ prompt_for_opt("Lowercase everything", &lcase_all);
+ if(!lcase_all)
+ prompt_for_opt("Lowercase mnemonics, hex", &lcase_opcodes);
+
+ prompt_for_opt("Replace leading spaces w/tabs", &leading_tabs);
+ if(!leading_tabs)
+ prompt_for_opt("Replace lead+inner spaces w/tabs", &all_tabs);
+
+ other = (chconsts_hex == CC_UNPRINT);
+ prompt_for_opt("Unprintable char consts to hex", &other);
+ if(other) {
+ chconsts_hex = CC_UNPRINT;
+ } else {
+ other = (chconsts_hex == CC_ALL);
+ prompt_for_opt("All char consts to hex", &other);
+ if(other)
+ chconsts_hex = CC_ALL;
+ else
+ chconsts_hex = CC_NONE;
+ }
+
+ if(chconsts_hex != CC_ALL)
+ prompt_for_opt("Close quote ' for char consts", &add_quote);
+
+ prompt_for_opt("Dump tokens in hex", &dump_tokens);
+ }
+
+ fflush(stdout);
+}
+#endif
+
+void usage() {
+ fprintf(stderr, "usage: " SELF " [options] inputfile\n\n");
+ fprintf(stderr, "options:\n");
+#ifndef ATARI8
+ fprintf(stderr, " -a Use ATASCII EOLs\n");
+ fprintf(stderr, " -c Convert non-printable char constants to hex\n");
+ fprintf(stderr, "-cc Convert all char constants to hex\n");
+#endif
+ fprintf(stderr, " -e nnn[,i] Renumber starting with nnn [increment i]\n");
+ fprintf(stderr, " -h Help (this text)\n");
+#ifndef ATARI8
+ fprintf(stderr, " -i Convert inverse video to normal\n");
+#endif
+ fprintf(stderr, " -l Lowercase mnemonics, hex constants\n");
+ fprintf(stderr, "-la Lowercase all, including comments\n");
+ fprintf(stderr, " -n No line numbers in output\n");
+ fprintf(stderr, " -o [file] Output to file (default = stdout)\n");
+ fprintf(stderr, " -p Omit leading . (period) from pseudo-ops\n");
+ fprintf(stderr, " -q Add closing quote (') to character constants\n");
+ fprintf(stderr, " -t Replace leading spaces with tabs\n");
+ fprintf(stderr, "-ta Replace spaces between all fields with tabs\n");
+ fprintf(stderr, " -v Verbose output (dump tokens)\n");
+#ifndef ATARI8
+ fprintf(stderr, " -m Print inverse video as underlined\n");
+ fprintf(stderr, " -r Print inverse video as ANSI reverse video\n");
+ fprintf(stderr, " -u Print ATASCII as Unicode/UTF-8\n");
+#endif
+ exit(1);
+}
+
+void get_renum_args(char *arg) {
+ renum_incr = 10;
+ /* atoi() doesn't detect errors, so: */
+ if(!arg || arg[0] > '9' || arg[0] < '0')
+ usage();
+ renum_start = atoi(arg);
+ arg = strchr(arg, ',');
+ if(arg) renum_incr = atoi(++arg);
+ if(!renum_incr || renum_incr < 0) usage();
+}
+
+/* TODO: support a few more -options
+ A lot of the fancier options I wanted to add, would require
+ a full parser for the grammar. I've avoided this partly because
+ it's more work, and partly because I dunno how well yacc/bison would
+ play with cc65...
+*/
+void handle_cli_opts(int argc, char **argv) {
+#ifdef ATARI8
+ infile[0] = '\0';
+#endif
+
+ while(++argv, --argc) {
+ if(argv[0][0] == '-') {
+ switch(tolower(argv[0][1])) {
+#ifndef ATARI8
+ case 'a':
+ nl = 0x9b;
+ if(argv[0][2]) usage();
+ break;
+
+ case 'i':
+ chconsts_hex = CC_UNPRINT;
+ deinverse = 1;
+ if(argv[0][2]) usage();
+ break;
+
+ case 'm':
+ inv_underscore = 1;
+ if(argv[0][2]) usage();
+ break;
+
+ case 'r':
+ inv_ansi = 1;
+ if(argv[0][2]) usage();
+ break;
+
+ case 'u':
+ unicode = 1;
+ if(argv[0][2]) usage();
+ break;
+#endif
+ case 'c':
+ chconsts_hex = CC_UNPRINT;
+ if(argv[0][2] == 'C' || argv[0][2] == 'c')
+ chconsts_hex = CC_ALL;
+ else if(argv[0][2]) usage();
+ break;
+
+ case 'e':
+ if(argv[0][2]) usage();
+ if(!argv[1]) usage();
+ get_renum_args(argv[1]);
+ argv++, argc--;
+ break;
+
+ case 'n':
+ no_numbers = 1;
+ if(argv[0][2]) usage();
+ break;
+
+ case 'h':
+ usage();
+ break;
+
+ case 'l':
+ lcase_opcodes = 1;
+ if(argv[0][2] == 'A' || argv[0][2] == 'a')
+ lcase_all = 1;
+ else if(argv[0][2]) usage();
+ break;
+
+ case 'p':
+ omit_dots = 1;
+ if(argv[0][2]) usage();
+ break;
+
+ case 'q':
+ add_quote = 1;
+ if(argv[0][2]) usage();
+ break;
+
+ case 't':
+ leading_tabs = 1;
+ if(argv[0][2] == 'A' || argv[0][2] == 'a')
+ all_tabs = 1;
+ else if(argv[0][2]) usage();
+ break;
+
+ case 'v':
+ dump_tokens = 1;
+ if(argv[0][2]) usage();
+ break;
+
+ case 'o':
+ if(argv[0][2]) {
+#ifdef ATARI8
+ strcpy(outfile, &argv[0][2]);
+#else
+ outfile = &argv[0][2];
+#endif
+ } else if(argc == 1) {
+ usage();
+ } else {
+ ++argv, --argc;
+#ifdef ATARI8
+ strcpy(outfile, argv[0]);
+#else
+ outfile = argv[0];
+#endif
+ }
+ using_stdout = 0;
+ break;
+
+ default:
+ usage();
+ break;
+ }
+ } else {
+#ifdef ATARI8
+ if(infile[0])
+ usage();
+ else
+ strcpy(infile, argv[0]);
+#else
+ if(infile)
+ usage();
+ else
+ infile = argv[0];
+#endif
+ }
+ }
+
+ if(!infile) usage();
+}
+
+int main(int argc, char **argv) {
+ fputs(BANNER, stderr);
+
+#ifdef ATARI8
+ while(1) {
+ if(argc < 2) {
+ atari8_get_opts();
+ } else {
+ handle_cli_opts(argc, argv);
+ argc = 1;
+ }
+#else
+ handle_cli_opts(argc, argv);
+#endif
+
+ input = fopen(infile, "rb");
+ if(!input) {
+ perror(infile);
+ exit_cleanly(1);
+ }
+
+ if(using_stdout) {
+ output = stdout;
+ } else {
+ output = fopen(outfile, "w");
+ if(!output) {
+ perror(outfile);
+ exit_cleanly(1);
+ }
+ }
+
+ if(renum_incr) renum_line = renum_start;
+ parse_header();
+ parse_lines();
+
+#ifdef ATARI8
+ }
+#endif
+
+ exit_cleanly(0);
+ return 0; /* to shut gcc up... */
+}