aboutsummaryrefslogtreecommitdiff
path: root/listbas.c
diff options
context:
space:
mode:
Diffstat (limited to 'listbas.c')
-rw-r--r--listbas.c1037
1 files changed, 932 insertions, 105 deletions
diff --git a/listbas.c b/listbas.c
index 000cc95..ab23fc3 100644
--- a/listbas.c
+++ b/listbas.c
@@ -8,45 +8,226 @@
#include <math.h>
#include <errno.h>
+#include <sys/wait.h>
+
#include "bas.h"
#include "bcdfp.h"
#include "tokens.h"
+#include "turbo_tokens.h"
+#include "aplus_tokens.h"
+#include "bxl_tokens.h"
+#include "bxe_tokens.h"
#include "atables.h"
+#include "whichbas.h"
+
+#define B_ATARI SRET_ATARI
+#define B_TURBO SRET_TURBO
+#define B_APLUS SRET_APLUS
+#define B_BXL SRET_BXL
+#define B_BXE SRET_BXE
+
+#define COLOR_FMT "\x1b[%d;3%dm" /* 1st %d is 1 for bold, 2nd is color */
+
+#define COLOR_OFF "\x1b[0m" /* clears inverse, too! */
+#define INV_OFF "\x1b[0m" /* clears color, too! */
+
+/* These would be better to use, allow turning off color and inverse
+ independently, but are not as widely supported by various terminals. */
+/* #define COLOR_OFF "\x1b[39;49m" */
+/* #define INV_OFF "\x1b[27m"" */
+#define INV_ON "\x1b[7m" /* goes all the way back to the original VT100 */
+#define ULINE_ON "\x1b[4m"
+
+#define MAG_INV "{inv}"
+#define MAG_NORM "{norm}"
+
+/* colors. */
+#define NO_COLOR -1
#define C_RED 1
#define C_GREEN 2
#define C_YELLOW 3
+#define C_BLUE 4
#define C_PURPLE 5
#define C_CYAN 6
+#define C_WHITE 7
+
+#define ENV_OPTS "LISTBAS_OPTS"
+#define ENV_MAX_ARGS 64
+/* #define DEBUG_ENV */
+
+/* output modes */
+#define M_UTF8 0 /* default */
+#define M_UTF8_I 1 /* -x */
+#define M_ATASCII 2 /* -a */
+#define M_MAG 3 /* -m */
+#define M_DOTS 4 /* -d */
-int immediate = 0, utf8 = 1, magazine = 0, inv = 0,
- color = 1, bold = 0, badtok = 0;
+const char *cmd_tokens[256];
+const char *op_tokens[256];
-const char **table = ata2utf;
+int bas_type = B_ATARI; /* -b */
+
+int output_mode = M_UTF8;
+
+int bold = 0; /* 1 with -B */
+int color = 1; /* 0 with -n */
+int immediate = 0; /* 1 with -i */
+int underline = 0; /* 1 with -u */
+int skip_lineno = 0; /* 1 with -l */
+int dump_tables = 0; /* 1 with -D */
+int autodetect = 1; /* 0 with -b */
+int mixed_case = 1; /* 0 with -k, or if input is B_ATARI or B_APLUS */
+int indent = 1; /* 0 with -t, or if input is B_ATARI */
+int startline = 0; /* -r */
+int endline = 32767; /* -r */
+
+/* change these with -c */
+int color_cmd = C_YELLOW;
+int color_op = C_GREEN;
+int color_func = C_PURPLE;
+int color_const = C_RED;
+int color_lineno = C_CYAN;
+int color_rem = C_BLUE;
+int color_data = C_CYAN;
+int color_varnames = NO_COLOR;
+
+const char *input_filename;
+
+int indent_level = 0;
+int if_without_then = 0;
+int badtok = 0; /* set to 1 if we find a bad token */
+int inv = 0; /* set to 1 when we're printing inverse */
+int cur_color = -1; /* -1 = no color */
+
+int first_stmt = 1; /* true if cmd token comes right after lineno */
FILE *outfh;
+int parse_color(char c) {
+ if(c == 'n')
+ return NO_COLOR;
+
+ if(c >= '0' && c <= '7')
+ return c - '0';
+
+ fprintf(stderr, "%s: Invalid color '%c'.\n", self, c);
+ exit(1);
+}
+
+void parse_color_scheme(const char *arg) {
+ if(!arg) return;
+
+ if(strlen(arg) != 8)
+ die("Color scheme must be 8 characters.");
+
+ color_cmd = parse_color(arg[0]);
+ color_op = parse_color(arg[1]);
+ color_func = parse_color(arg[2]);
+ color_const = parse_color(arg[3]);
+ color_lineno = parse_color(arg[4]);
+ color_rem = parse_color(arg[5]);
+ color_data = parse_color(arg[6]);
+ color_varnames = parse_color(arg[7]);
+}
+
+int get_bas_type(char *arg) {
+ if(arg[0] == 't')
+ return B_TURBO;
+
+ if(arg[0] == 'a') {
+ if(arg[1] == '+')
+ return B_APLUS;
+ else
+ return B_ATARI;
+ }
+
+ if(arg[0] == 'x') {
+ if(arg[1] == 'l')
+ return B_BXL;
+ else if(arg[1] == 'e')
+ return B_BXE;
+ }
+
+ fprintf(stderr, "%s: Invalid BASIC type for -b option: %s\n", self, arg);
+ exit(1);
+}
+
+void get_line_range(const char *arg) {
+ int val = 0, comma = 0;
+ const char *p = arg;
+
+ while(*p) {
+ if(*p >= '0' && *p <= '9') {
+ val *= 10;
+ val += *p - '0';
+ if(val > 32768) die("Invalid line number for -r (range is 0-32768).");
+ } else if(*p == ',' || *p == '-') {
+ if(comma) die("Invalid argument for -r (too many commas).");
+ comma++;
+ startline = val;
+ val = 0;
+ } else {
+ if(comma) die("Invalid argument for -r (only digits and comma allowed).");
+ }
+ p++;
+ }
+
+ if(comma)
+ endline = val ? val : 32767;
+ else
+ startline = endline = val;
+
+ if(endline < startline)
+ die("Invalid argument for -r (end > start).");
+}
+
void print_help(void) {
- printf("Usage: %s [-a] [-b] [-i] [-m] [-n] [-v] <inputfile>\n", self);
+ printf("Usage: %s [-a|-d|-m|-x|-U] [-B] [-i] [-l] [-u] [-n|-C] [-v] [-c *colors*] [-r *start,end* ] [-k] [-t] <inputfile>\n", self);
+ printf(" -b <XX>: set BASIC type. XX is: a = atari, t = turbo, xl, xe, a+.\n");
+ printf(" -U: output ATASCII as Unicode/UTF-8 (this is the default).\n");
printf(" -a: output raw ATASCII.\n");
- printf(" -b: use bold for color output.\n");
- printf(" -i: show immediate mode command (line 32768).\n");
+ printf(" -d: use dots instead of Unicode/UTF-8.\n");
printf(" -m: magazine style listing (see a8cat(1)).\n");
+ printf(" -x: XL international character set (UTF-8).\n");
+ printf(" -B: use bold for color output.\n");
+ printf(" -i: show immediate mode command (line 32768).\n");
+ printf(" -l: don't print line numbers.\n");
+ printf(" -C: enable color syntax highlighting (this is the default).\n");
printf(" -n: disable color syntax highlighting.\n");
+ printf(" -u: use underline for inverse video.\n");
+ printf(" -c: use custom colors (see man page).\n");
+ printf(" -k: disable mixed case keywords for BXL/BXE (e.g. Print).\n");
+ printf(" -t: disable Turbo/BXL/BXE indentation.\n");
+ printf(" -r: only list lines numbered from *start* to *end*.\n");
printf(" -v: verbose.\n");
}
-void parse_args(int argc, char **argv) {
+void parse_args(int argc, char **argv, int from_env) {
int opt;
- while( (opt = getopt(argc, argv, "viamnbh")) != -1) {
+ optind = 1;
+
+ while( (opt = getopt(argc, argv, "r:Db:UCviamnBdhxulc:kt")) != -1) {
switch(opt) {
- case 'v': verbose = 1; break;
- case 'i': immediate = 1; break;
- case 'a': utf8 = magazine = 0; color = 0; break;
- case 'm': utf8 = 0 ; magazine = 1; color = 0; table = ata2mag; break;
- case 'n': color = 0; break;
- case 'b': bold = 1; break;
+ case 'U': output_mode = M_UTF8; break;
+ case 'a': output_mode = M_ATASCII; break;
+ case 'm': output_mode = M_MAG; break;
+ case 'd': output_mode = M_DOTS; break;
+ case 'x': output_mode = M_UTF8_I; break;
+ case 'D': dump_tables = 1; break;
+ case 'v': verbose = 1; break;
+ case 'i': immediate = 1; break;
+ case 'B': bold = 1; break;
+ case 'u': underline = 1; break;
+ case 'C': color = 1; break;
+ case 'n': color = 0; break;
+ case 'l': skip_lineno = 1; break;
+ case 'k': mixed_case = 0; break;
+ case 't': indent = 0; break;
+ case 'b': autodetect = 0; bas_type = get_bas_type(optarg); break;
+ case 'r': get_line_range(optarg); break;
+ case 'c': parse_color_scheme(optarg); break;
case 'h': print_help(); exit(0);
default:
print_help();
@@ -54,19 +235,74 @@ void parse_args(int argc, char **argv) {
}
}
- if(optind >= argc)
- die("No input file given (use - for stdin).");
- else
- open_input(argv[optind]);
+ if(output_mode == M_ATASCII) color = 0;
+
+ if(!from_env) {
+ if(optind >= argc) {
+ die("No input file given (use - for stdin).");
+ } else {
+ input_filename = argv[optind];
+ open_input(input_filename);
+ }
+ }
+}
+
+/* make a fake argv and argc */
+void parse_env_args(void) {
+ int fargc;
+ char *fargv[ENV_MAX_ARGS + 1];
+ char *env, *p;
+
+ env = getenv(ENV_OPTS);
+ if(!env) return;
+
+#ifdef DEBUG_ENV
+ fprintf(stderr, "%s: " ENV_OPTS " is set in the env, using it.\n", self);
+#endif
+
+ fargv[0] = (char *)self;
+ fargv[1] = env;
+ fargc = 2;
+
+ for(p = env; *p; p++) {
+ if(*p == ' ' || *p == '\t') {
+ *p = '\0';
+ if(fargc == ENV_MAX_ARGS) {
+ /* not gonna bother with the man page for this, it's
+ long enough already. it's a pathological case. */
+ fprintf(stderr, "%s: Too many spaces in " ENV_OPTS " (max %d)\n", self, ENV_MAX_ARGS - 1);
+ break;
+ }
+ fargv[fargc++] = p + 1;
+ }
+ }
+
+ fargv[fargc] = NULL;
+
+#ifdef DEBUG_ENV
+ {
+ char **p;
+ fprintf(stderr, "%s: Read options from environment:\n", self);
+ for(p = fargv + 1; *p; p++) {
+ fprintf(stderr, "\t%s\n", *p);
+ }
+ }
+#endif
+
+ parse_args(fargc, fargv, 1);
}
void setup_outfh(void) {
- if(! (utf8 || magazine) ) {
+ if(output_mode == M_ATASCII) {
if(isatty(fileno(stdout))) {
die("Refusing to write ATASCII data to the terminal.");
}
}
- outfh = stdout;
+ outfh = freopen(NULL, "wb", stdout);
+ if(!outfh) {
+ perror("freopen()");
+ die("Can't reopen standard output in binary mode.");
+ }
return;
}
@@ -99,17 +335,28 @@ double bcd2double(const unsigned char *num) {
return result;
}
-void color_on(unsigned char c) {
- printf("\x1b[%d;3%dm", bold, c);
+void color_on(int c) {
+ cur_color = c;
+ if(c != NO_COLOR)
+ printf(COLOR_FMT, bold, c);
}
void color_off(void) {
- fputs("\x1b[0m", outfh);
+ cur_color = -1;
+ fputs(COLOR_OFF, outfh);
}
-void print_number(unsigned int pos) {
- if(color) color_on(C_RED);
- fprintf(outfh, "%G", bcd2double(program + pos));
+void print_number(unsigned int pos, int hex) {
+ double num = bcd2double(program + pos);
+ if(color) color_on(color_const);
+ if(hex) {
+ if(num < 0 || num > 0xffff) {
+ fprintf(stderr, "%s: invalid Turbo/BXL/BXE hex const %lf at pos $%04x\n", self, num, pos);
+ }
+ fprintf(outfh, (num > 0xff ? "%04x" : "%02x"), (unsigned int)num);
+ } else {
+ fprintf(outfh, "%G", num);
+ }
if(color) color_off();
}
@@ -120,22 +367,22 @@ void print_number(unsigned int pos) {
need to be printed that way. */
int affects_inv(unsigned char c) {
switch(c) {
- case 0x1b:
- case 0x1c:
- case 0x1d:
- case 0x1e:
- case 0x1f:
- case 0x9b:
- case 0x9c:
- case 0x9d:
- case 0x9e:
- case 0x9f:
- case 0x7d:
- case 0x7e:
- case 0x7f:
- case 0xfd:
- case 0xfe:
- case 0xff:
+ case 0x1b: /* esc */
+ case 0x1c: /* up */
+ case 0x1d: /* down */
+ case 0x1e: /* left */
+ case 0x1f: /* right */
+ case 0x9b: /* EOL */
+ case 0x9c: /* del line */
+ case 0x9d: /* ins line */
+ case 0x9e: /* clear tab */
+ case 0x9f: /* set tab */
+ case 0x7d: /* cls */
+ case 0x7e: /* BS */
+ case 0x7f: /* tab */
+ case 0xfd: /* bell */
+ case 0xfe: /* del chr */
+ case 0xff: /* ins chr */
return 0;
default:
return 1;
@@ -143,26 +390,48 @@ int affects_inv(unsigned char c) {
}
void start_inv(unsigned char c) {
- if(utf8) {
- fputs("\x1b[7m", outfh);
- inv = 1;
- } else if(magazine) {
- if(affects_inv(c)) {
- fputs("{inv}", outfh);
+ switch(output_mode) {
+ case M_DOTS:
+ case M_UTF8:
+ case M_UTF8_I:
+ if(underline)
+ fputs(ULINE_ON, outfh);
+ else
+ fputs(INV_ON, outfh);
inv = 1;
- }
+ break;
+ case M_MAG:
+ if(affects_inv(c)) {
+ fputs(MAG_INV, outfh);
+ inv = 1;
+ }
+ break;
+ case M_ATASCII:
+ default:
+ break;
}
}
void end_inv(unsigned char c) {
- if(utf8) {
- fputs("\x1b[0m", outfh);
- inv = 0;
- } else if(magazine) {
- if(affects_inv(c)) {
- fputs("{norm}", outfh);
+ switch(output_mode) {
+ case M_DOTS:
+ case M_UTF8:
+ case M_UTF8_I:
+ fputs(INV_OFF, outfh);
inv = 0;
- }
+ /* work around INV_OFF also turning off color. */
+ if(cur_color > -1)
+ color_on(cur_color);
+ break;
+ case M_MAG:
+ if(affects_inv(c)) {
+ fputs(MAG_NORM, outfh);
+ inv = 0;
+ }
+ break;
+ case M_ATASCII:
+ default:
+ break;
}
}
@@ -176,37 +445,449 @@ void print_ata_chr(unsigned char c) {
end_inv(c);
}
}
- fputs(table[c & 0x7f], outfh);
+
+ switch(output_mode) {
+ case M_DOTS:
+ fputc(isprint(c & 0x7f) ? c & 0x7f : '.', outfh);
+ break;
+ case M_UTF8:
+ fputs(ata2utf[c & 0x7f], outfh);
+ break;
+ case M_UTF8_I:
+ fputs(ics2utf[c & 0x7f], outfh);
+ break;
+ case M_MAG:
+ fputs(ata2mag[c & 0x7f], outfh);
+ break;
+ case M_ATASCII:
+ default:
+ outchr(c);
+ break;
+ }
+}
+
+void print_mixed_case(const char *name) {
+ char c;
+
+ if(!*name) return;
+ if(*name == ' ')
+ outchr(*name++);
+ outchr(*name++);
+ while(( c = *name++)) {
+ if(c >= 'A' && c <= 'Z')
+ c |= 0x20;
+ if(color && c == '(' && cur_color != color_op) color_on(color_op);
+ outchr(c);
+ }
}
void print_string(unsigned int pos, unsigned int len) {
inv = 0;
- if(color) color_on(C_RED);
+ if(color) color_on(color_op);
outchr('"');
+ if(color) color_on(color_const);
while(len--) print_ata_chr(program[pos++]);
- if(inv) end_inv(0);
+ if(inv) {
+ end_inv(0);
+ }
+ if(color) color_on(color_op);
outchr('"');
if(color) color_off();
}
+const char *get_bxl_ext_name(unsigned char tok) {
+ switch(tok) {
+ case 0x10:
+ return "Local";
+ case 0x11:
+ return "Exit";
+ case 0x12:
+ return "Procedure";
+ case 0x13:
+ return "Call";
+ case 0x14:
+ return "Sortup";
+ case 0x15:
+ return "Sortdown";
+ default:
+ return "(bad BXL ext token)";
+ }
+}
+
+void clear_callbacks(void);
+void init_callbacks(void);
+
+int cmd_is_if;
+
+CALLBACK(find_cmd_if) {
+ cmd_is_if = 0;
+ if((bas_type == B_APLUS && tok == 0x06) || (bas_type != B_APLUS && tok == CMD_IF)) {
+ cmd_is_if = 1;
+ if_without_then++; /* find_then() decrements it, if there's a THEN */
+ }
+}
+
+CALLBACK(find_op_then) {
+ if(verbose)
+ fprintf(stderr, "find_op_then lineno %d, pos %04x, tok %02x\n", lineno, pos, tok);
+
+ if(cmd_is_if && tok == OP_THEN)
+ if_without_then--;
+}
+
+/* walk_code()'s API isn't really set up to be reentrant, but we
+ can do one level of sub-walk. */
+void find_then(int lineno, unsigned short pos) {
+ clear_callbacks();
+ on_exp_token = find_op_then;
+ on_cmd_token = find_cmd_if;
+ walk_code(lineno, lineno);
+ init_callbacks();
+}
+
CALLBACK(print_lineno) {
- if(color) color_on(C_CYAN);
+ unsigned char cmd_tok;
+
+ first_stmt = 1;
+
+ if(indent) {
+ cmd_tok = program[pos + 4];
+ if_without_then = 0;
+ if((bas_type == B_APLUS && cmd_tok == 0x06) || (bas_type != B_APLUS && cmd_tok == CMD_IF)) {
+ find_then(lineno, pos);
+ }
+ }
+
+ if(skip_lineno) return;
+ if(color) color_on(color_lineno);
fprintf(outfh, "%d ", lineno);
if(color) color_off();
}
+/* indentation is totally different between A+, BXL, and BXE.
+ Turbo and BXE use the same indent style, but Turbo has more
+ keywords that indent (e.g. REPEAT, DO).
+
+ A+ always indents on the lines after a WHILE or a FOR, unless there's
+ a matching ENDWHILE or NEXT on the same line. It indents the lines
+ after IF only if there's no THEN and no matching ENDIF on the
+ same line. ENDWHILE, NEXT, ENDIF all un-undent one level. The style
+ is that the the ending keyword (or ELSE) does NOT get unindented,
+ itself:
+
+ WHILE 1
+ PRINT "OK"
+ ENDWHILE
+ PRINT "HEY"
+
+ That looks *wrong* to me (the ENDWHILE should line up with the WHILE), but
+ that's how A+ works.
+
+ BXL's style is to indent the entire block to the same level:
+
+ While 1
+ Print "OK"
+ Endwhile
+ Print "HEY"
+
+ This applies even if the whole block's on one line:
+
+ ? "OK"
+ For I=1 To 10:Next I
+ ? "OK"
+
+BXE and Turbo work like modern style: the keywords that start and end the
+block are not indented, only the lines between them.
+
+ While 1
+ Print "OK"
+ Endwhile
+ Print "HEY"
+
+ */
+
+void print_indent(void) {
+ int i;
+ if(indent_level < 0) indent_level = 0;
+ for(i = 0; i < indent_level; i++) {
+ outchr(' ');
+ outchr(' ');
+ }
+}
+
+void aplus_indent_line(const unsigned char tok) {
+ if(first_stmt) print_indent();
+
+ switch(tok) {
+ case 0x07: /* FOR */
+ case 0x13: /* WHILE */
+ indent_level++;
+ return;
+ case 0x08: /* NEXT */
+ case 0x14: /* ENDWHILE */
+ case 0x18: /* ENDIF */
+ indent_level--;
+ return;
+ default: break;
+ }
+ if(if_without_then) {
+ indent_level++;
+ if_without_then = 0;
+ }
+}
+
+void turbo_indent_line(const unsigned char tok) {
+ switch(tok) {
+ case 0x40: /* ELSE */
+ if(first_stmt) {
+ /* gets "outdented" */
+ indent_level--;
+ print_indent();
+ indent_level++;
+ }
+ return;
+ case CMD_FOR: /* FOR */
+ case 0x3c: /* REPEAT */
+ case 0x3e: /* WHILE */
+ case 0x45: /* DO */
+ case 0x4f: /* PROC */
+ if(first_stmt) print_indent();
+ indent_level++;
+ return;
+ case CMD_NEXT: /* NEXT */
+ case 0x3d: /* UNTIL */
+ case 0x3f: /* WEND */
+ case 0x41: /* ENDIF */
+ case 0x46: /* LOOP */
+ case 0x51: /* ENDPROC */
+ indent_level--;
+ break;
+ default: break;
+ }
+ if(first_stmt) print_indent();
+ if(if_without_then) {
+ indent_level++;
+ if_without_then = 0;
+ }
+}
+
+/* Note: BXL's PROCEDURE/EXIT (from toolkit extensions) does
+ *not* get indented. */
+void bxl_indent_line(unsigned char tok) {
+ if(if_without_then) {
+ indent_level++;
+ if_without_then = 0;
+ }
+ switch(tok) {
+ case CMD_FOR: /* FOR */
+ case 0x38: /* WHILE */
+ indent_level++;
+ break;
+ case CMD_NEXT: /* NEXT */
+ case 0x39: /* ENDWHILE */
+ case 0x3d: /* ENDIF */
+ if(first_stmt) print_indent();
+ indent_level--;
+ return;
+ default: break;
+ }
+ if(first_stmt) print_indent();
+}
+
+void bxe_indent_line(const unsigned char tok) {
+ switch(tok) {
+ case 0x3c: /* ELSE */
+ if(first_stmt) {
+ /* gets "outdented" */
+ indent_level--;
+ print_indent();
+ indent_level++;
+ }
+ return;
+ case CMD_FOR: /* FOR */
+ case 0x38: /* WHILE */
+ case 0x59: /* PROCEDURE */
+ if(first_stmt) print_indent();
+ indent_level++;
+ return;
+ case CMD_NEXT: /* NEXT */
+ case 0x39: /* ENDWHILE */
+ case 0x5e: /* EXIT */
+ case 0x3d: /* ENDIF */
+ indent_level--;
+ break;
+ default: break;
+ }
+ if(first_stmt) print_indent();
+ if(if_without_then) {
+ if_without_then = 0;
+ indent_level++;
+ }
+}
+
+void indent_line(const unsigned char tok) {
+ if(!indent) return;
+
+ switch(bas_type) {
+ case B_APLUS: aplus_indent_line(tok); return;
+ case B_TURBO: turbo_indent_line(tok); return;
+ case B_BXL: bxl_indent_line(tok); return;
+ case B_BXE: bxe_indent_line(tok); return;
+ case B_ATARI:
+ default:
+ return;
+ }
+}
+
CALLBACK(print_cmd) {
const char *name;
- if(tok == CMD_ILET) return;
+ indent_line(tok);
+ if(first_stmt)
+ first_stmt = 0;
+
+ if(bas_type == B_APLUS) {
+ if(tok == 0x52) return;
+ } else {
+ if(tok == CMD_ILET) return;
+ }
+
+ if(color) color_on(color_cmd);
- if(color) color_on(C_YELLOW);
- if(tok > last_command || (!(name = commands[tok]))) {
+ if((!(name = cmd_tokens[tok]))) {
fprintf(outfh, "(bad cmd token $%02x) ", tok);
badtok = 1;
} else {
- fprintf(outfh, "%s ", name);
+ if(bas_type == B_BXL && tok == 0x5a)
+ name = get_bxl_ext_name(program[pos + 1]);
+
+ if(mixed_case && (bas_type == B_BXL || bas_type == B_BXE)) {
+ print_mixed_case(name);
+ outchr(' ');
+ } else {
+ fprintf(outfh, "%s ", name);
+ }
}
+
+ if(color) color_off();
+}
+
+void aplus_op_color_on(unsigned char tok) {
+ int c = color_op;
+
+ if((tok >= 0x17 && tok <= 0x1c) || (tok >= 0x29 && tok <= 0x2b)) {
+ /* keywords */
+ c = color_cmd;
+ } else if(tok >= 0x40) {
+ /* functions */
+ c = color_func;
+ } else if(tok == 0x38 || tok == 0x39) {
+ /* unary +/- */
+ c = color_const;
+ }
+
+ color_on(c);
+}
+
+/* pseudo-funcs listed here are ones that don't take (), e.g.
+ X=RND or A$=INKEY$. right now they're colored the same as regular
+ functions, but it might make sense to color them as variables?
+ also, %0 %1 %2 %3 (tokens 0x66 to 0x69) are currently op-colored,
+ perhaps they should be constant-colored? */
+void turbo_op_color_on(unsigned char tok) {
+ switch(tok) {
+ case 0x55:
+ case 0x58:
+ case 0x59: /* pseudo-func */
+ case 0x5b:
+ case 0x5c:
+ case 0x5e:
+ case 0x5f: /* pseudo-func */
+ case 0x60: /* pseudo-func */
+ case 0x63: /* pseudo-func */
+ case 0x64:
+ case 0x65:
+ case 0x6b:
+ case 0x6c: /* pseudo-func */
+ case 0x6d: /* pseudo-func */
+ color_on(color_func);
+ break;
+ case 0x5a:
+ case 0x5d:
+ case 0x61:
+ case 0x62:
+ case 0x6a:
+ color_on(color_cmd);
+ break;
+ default:
+ color_on(color_op);
+ break;
+ }
+}
+
+/* BXL and BXE use the same operator tokens */
+void bxlxe_op_color_on(unsigned char tok) {
+ int c = color_op;
+
+ if(tok == 0x55) {
+ c = color_cmd;
+ } else if(tok >= 0x5a) {
+ c = color_func;
+ }
+
+ color_on(c);
+}
+
+void op_color_on(unsigned char tok) {
+ if(!color) return;
+
+ if(bas_type == B_APLUS) {
+ aplus_op_color_on(tok);
+ return;
+ }
+
+ /* common ops for Atari, Turbo, BXL, BXE */
+ if(tok <= last_operator) {
+ if(tok > 0x3c)
+ color_on(color_func);
+ else if(tok == OP_UMINUS || tok == OP_UPLUS)
+ color_on(color_const); /* show leading sign in same color as the number */
+ else if((tok >= 0x17 && tok <= 0x1b) || (tok >= 0x28 && tok <= 0x2a))
+ color_on(color_cmd);
+ else
+ color_on(color_op);
+ return;
+ }
+
+ if(bas_type == B_TURBO)
+ turbo_op_color_on(tok);
+ else if(bas_type == B_BXL || bas_type == B_BXE)
+ bxlxe_op_color_on(tok);
+ else
+ color_on(color_op); /* in case of bad token when bas_type == B_BASIC */
+}
+
+void print_varname(unsigned char varnum) {
+ int i, count;
+ unsigned char c;
+
+ if(color) color_on(color_varnames);
+
+ for(i = vnstart, count = 0; count < varnum; i++) {
+ if(program[i] & 0x80) count++;
+ if(i == codestart) {
+ fprintf(outfh, "(bad var tok $%02x)", varnum | 0x80);
+ return;
+ }
+ }
+
+ do {
+ c = program[i++];
+ if(color && c == ('(' | 0x80)) {
+ if(color) color_on(color_op);
+ }
+ outchr(c & 0x7f);
+ } while (c < 0x80);
if(color) color_off();
}
@@ -214,8 +895,21 @@ CALLBACK(print_op) {
const char *name;
switch(tok) {
+ case 0: /* Turbo variables numbered >= $80 */
+ if(bas_type == B_TURBO) {
+ print_varname(program[pos + 1] | 0x80);
+ return;
+ } else {
+ fprintf(stderr, "%s: got Turbo ext var tok at line %d, in non-Turbo program!\n", self, lineno);
+ }
+ break;
+ case OP_HEXCONST:
+ if(color) color_on(color_op);
+ outchr('$');
+ print_number(pos + 1, 1);
+ return;
case OP_NUMCONST:
- print_number(pos + 1);
+ print_number(pos + 1, 0);
return;
case OP_STRCONST:
print_string(pos + 2, program[pos + 1]);
@@ -225,70 +919,53 @@ CALLBACK(print_op) {
default: break;
}
- if(color) {
- if(tok > 0x3c)
- color_on(C_PURPLE);
- else if(tok == OP_UMINUS)
- color_on(C_RED); /* show leading - in same color as the number */
- else if((tok >= 0x17 && tok <= 0x1b) || (tok >= 0x28 && tok <= 0x2a))
- color_on(C_YELLOW);
- else
- color_on(C_GREEN);
- }
- if(tok > last_operator || (!(name = operators[tok]))) {
+ if(color) op_color_on(tok);
+
+ if((!(name = op_tokens[tok]))) {
fprintf(outfh, "(bad op token $%02x)", tok);
badtok = 1;
} else {
- fprintf(outfh, "%s", name);
+ if(bas_type == B_BXL || bas_type == B_BXE)
+ print_mixed_case(name);
+ else
+ fprintf(outfh, "%s", name);
}
if(color) color_off();
}
-CALLBACK(print_varname) {
- int i, count;
- unsigned char c, c_on = 0;;
-
- tok &= 0x7f;
- for(i = vnstart, count = 0; count < tok; i++) {
- if(program[i] & 0x80) count++;
- }
- do {
- c = program[i++];
- if(color && c == ('(' | 0x80)) {
- color_on(C_GREEN);
- c_on = 1;
- }
- outchr(c & 0x7f);
- } while (c < 0x80);
- if(c_on) color_off();
+/* for normal BASIC/A+/XL/XE variable tokens, $80-$FF. Turbo uses
+ these, too, but it supports 256 variables, so it only uses $80-$FF
+ for the first 128 vars. */
+CALLBACK(print_std_varname) {
+ print_varname(tok & 0x7f);
}
CALLBACK(print_text) {
unsigned char c, is_data = program[pos - 1] == CMD_DATA, comma = 0;
inv = 0;
- if(color) color_on(C_CYAN);
+ if(color) color_on(is_data ? color_data : color_rem);
while(program[pos] != 0x9b) {
c = program[pos++];
if(color && is_data && c == ',') {
- color_off();
+ color_on(color_op);
comma = 1;
} else {
comma = 0;
}
print_ata_chr(c);
if(comma)
- color_on(C_CYAN);
+ color_on(is_data ? color_data : color_rem);
}
if(inv) end_inv(0);
if(color) color_off();
}
CALLBACK(print_newline) {
- if(utf8 || magazine)
- outchr('\n');
- else
+ if(output_mode == M_ATASCII)
outchr(0x9b);
+ else
+ outchr('\n');
}
CALLBACK(code_prot) {
@@ -296,21 +973,171 @@ CALLBACK(code_prot) {
exit(0);
}
-void list(void) {
+void clear_callbacks(void) {
+ on_start_line = 0;
+ on_cmd_token = 0;
+ on_exp_token = 0;
+ on_var_token = 0;
+ on_end_line = 0;
+ on_text = 0;
+ on_bad_line_length = 0;
+}
+
+void init_callbacks(void) {
on_start_line = print_lineno;
on_cmd_token = print_cmd;
on_exp_token = print_op;
- on_var_token = print_varname;
+ on_var_token = print_std_varname; /* see also: print_turbo_varname() */
on_end_line = print_newline;
on_text = print_text;
on_bad_line_length = code_prot;
- walk_code(0, 32767 + immediate);
+}
+
+void list(void) {
+ init_callbacks();
+ if(endline == 32767 && immediate) endline++;
+ walk_code(startline, endline);
+}
+
+void init_bas_tables() {
+ memmove(cmd_tokens, commands, (last_command + 1) * sizeof(char *));
+ memmove(op_tokens, operators, (last_operator + 1) * sizeof(char *));
+}
+
+void init_aplus_tables() {
+ memmove(cmd_tokens, aplus_cmds, aplus_cmd_size);
+ memmove(op_tokens, aplus_ops, aplus_ops_size);
+}
+
+void init_turbo_tables() {
+ memmove(cmd_tokens + last_command + 1, turbo_cmds, turbo_cmd_size);
+ memmove(op_tokens + last_operator + 1, turbo_ops, turbo_ops_size);
+}
+
+void init_bxl_tables() {
+ memmove(cmd_tokens + last_command + 1, bxl_cmds, bxl_cmd_size);
+ memmove(op_tokens + last_operator + 1, bxl_ops, bxl_ops_size);
+}
+
+/* BXE's token table is identical to BXL's, and the commands are the
+ same up to token 0x56 (FAST). */
+void init_bxe_tables() {
+ init_bxl_tables();
+ memmove(cmd_tokens + 0x57, bxe_cmds, bxe_cmd_size);
+}
+
+void init_token_tables() {
+ if(bas_type == B_APLUS) {
+ init_aplus_tables();
+ return;
+ }
+
+ init_bas_tables();
+
+ if(bas_type == B_TURBO)
+ init_turbo_tables();
+ else if(bas_type == B_BXL)
+ init_bxl_tables();
+ else if(bas_type == B_BXE)
+ init_bxe_tables();
+
+ if(dump_tables) {
+ int i;
+ printf("commands:\n\n");
+ for(i = 0; i < 0x65; i++)
+ printf("%s $%02x\n", (cmd_tokens[i] ? cmd_tokens[i] : "(null)"), i);
+ printf("operators:\n\n");
+ for(i = 0x12; i < 0x69; i++)
+ printf("%s $%02x\n", (op_tokens[i] ? op_tokens[i] : "(null)"), i);
+ exit(0);
+ }
+}
+
+void set_bas_dialect(int d) {
+ if(verbose)
+ fprintf(stderr, "set_bas_dialect(%d)\n", d);
+
+ switch(d) {
+ case 0:
+ case 1:
+ die("whichbas child process failed");
+ break;
+ case SRET_ATARI:
+ case SRET_APLUS:
+ case SRET_TURBO:
+ case SRET_BXL:
+ case SRET_BXE:
+ bas_type = d;
+ break;
+ default:
+ fprintf(stderr, "whichbas results ambiguous; guessing Turbo BASIC\n");
+ bas_type = SRET_TURBO;
+ break;
+ }
+}
+
+void detect_bas_dialect() {
+ pid_t pid, status;
+ int wstatus;
+ const char *args[4];
+
+ args[0] = "whichbas";
+ args[1] = "-s";
+ args[2] = input_filename;
+ args[3] = 0;
+
+ pid = fork();
+ if(pid == -1) {
+ perror("fork()");
+ die("Can't spawn child process");
+ } else if(pid) {
+ /* we are the parent */
+ status = waitpid(pid, &wstatus, 0);
+ if(status < 0) {
+ perror("waitpid()");
+ die("Child process went south");
+ }
+ if(!WIFEXITED(wstatus)) {
+ die("Child process went south");
+ }
+ set_bas_dialect(WEXITSTATUS(wstatus));
+ } else {
+ /* we are the child */
+ if(execvp(args[0], (char * const *)args) < 0) {
+ perror("Can't execute whichbas");
+ exit(1);
+ }
+ }
+}
+
+void init_bas_dialect() {
+ if(autodetect)
+ detect_bas_dialect();
+
+ if(bas_type == B_TURBO || bas_type == B_BXL || bas_type == B_BXE)
+ allow_hex_const = 1;
+
+ if(bas_type == B_APLUS)
+ aplus_errtok_hack = 1;
+
+ if(bas_type == B_BXL)
+ bxl_exttok_hack = 1;
+
+ if(bas_type == B_ATARI || bas_type == B_APLUS || bas_type == B_TURBO)
+ mixed_case = 0;
+
+ if(bas_type == B_ATARI)
+ indent = 0;
}
int main(int argc, char **argv) {
set_self(*argv);
parse_general_args(argc, argv, print_help);
- parse_args(argc, argv);
+ parse_env_args();
+ parse_args(argc, argv, 0);
+
+ init_bas_dialect();
+ init_token_tables();
readfile();
parse_header();
@@ -322,7 +1149,7 @@ int main(int argc, char **argv) {
list();
if(badtok) {
- fprintf(stderr, "%s: program has unknown tokens; maybe Turbo BASIC or BASIC XL/XE?\n", self);
+ fprintf(stderr, "%s: Program has unknown tokens; wrong BASIC dialect?\n", self);
return 2;
}