diff options
Diffstat (limited to 'listbas.c')
-rw-r--r-- | listbas.c | 1078 |
1 files changed, 1011 insertions, 67 deletions
@@ -8,31 +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 */ -int immediate = 0, a8cat = 1, magazine = 0; +/* 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 */ + +const char *cmd_tokens[256]; +const char *op_tokens[256]; + +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 [-v] [-i] [-a|-u] <inputfile>\n", self); - printf(" -v: verbose.\n"); - printf(" -i: show immediate mode command (line 32768).\n"); + 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(" -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, "viamh")) != -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': a8cat = magazine = 0; break; - case 'm': a8cat = magazine = 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(); @@ -40,45 +235,75 @@ 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); + } + } } -void setup_outfh(void) { - const char *cmd; +/* make a fake argv and argc */ +void parse_env_args(void) { + int fargc; + char *fargv[ENV_MAX_ARGS + 1]; + char *env, *p; - /* search current dir before PATH. no easy way to detect errors here, - have to wait until we call pclose(). */ - if(a8cat) { - if(magazine) - cmd = "./a8cat -m 2>/dev/null || a8cat -m 2>/dev/null || exit 1"; - else - cmd = "./a8cat 2>/dev/null || a8cat 2>/dev/null || exit 1"; - } else { - if(isatty(fileno(stdout))) { - die("Refusing to write ATASCII data to the terminal."); + 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; } - outfh = stdout; - return; } - outfh = popen(cmd, "w"); - if(!outfh) { - /* fork() or pipe() failed. does NOT detect if the command - wasn't found. */ - perror(self); - exit(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 close_outfh(void) { - if(a8cat) { - if(pclose(outfh)) { - die("output filter failed; a8cat not in current dir or $PATH."); +void setup_outfh(void) { + if(output_mode == M_ATASCII) { + if(isatty(fileno(stdout))) { + die("Refusing to write ATASCII data to the terminal."); } } + outfh = freopen(NULL, "wb", stdout); + if(!outfh) { + perror("freopen()"); + die("Can't reopen standard output in binary mode."); + } + return; } void outchr(char c) { @@ -110,37 +335,581 @@ double bcd2double(const unsigned char *num) { return result; } -void print_number(unsigned int pos) { - fprintf(outfh, "%G", bcd2double(program + pos)); +void color_on(int c) { + cur_color = c; + if(c != NO_COLOR) + printf(COLOR_FMT, bold, c); +} + +void color_off(void) { + cur_color = -1; + fputs(COLOR_OFF, outfh); +} + +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(); +} + +/* only called in magazine mode. cursor control characters + like a bell in the middle of a non-inverse string + should not cause it to print {inv}{bell}{norm}. The {bell} + is "inherently" inverse (since its high bit is set) but doesn't + need to be printed that way. */ +int affects_inv(unsigned char c) { + switch(c) { + 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; + } +} + +void start_inv(unsigned char c) { + 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) { + 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; + } +} + +void print_ata_chr(unsigned char c) { + if(c & 0x80) { + if(!inv) { + start_inv(c); + } + } else { + if(inv) { + end_inv(c); + } + } + + 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(color_op); outchr('"'); - while(len--) outchr(program[pos++]); + if(color) color_on(color_const); + while(len--) print_ata_chr(program[pos++]); + 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) { + 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(tok > last_command || (!(name = commands[tok]))) + if((!(name = cmd_tokens[tok]))) { fprintf(outfh, "(bad cmd token $%02x) ", tok); + badtok = 1; + } else { + 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 - fprintf(outfh, "%s ", name); + 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(); } 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]); @@ -150,54 +919,225 @@ CALLBACK(print_op) { default: break; } - 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); - else - fprintf(outfh, "%s", name); + badtok = 1; + } else { + 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; - - tok &= 0x7f; - for(i = vnstart, count = 0; count < tok; i++) { - if(program[i] & 0x80) count++; - } - do { - outchr( (c = program[i++]) & 0x7f); - } while (c < 0x80); +/* 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) { - while(program[pos] != 0x9b) outchr(program[pos++]); + unsigned char c, is_data = program[pos - 1] == CMD_DATA, comma = 0; + + inv = 0; + if(color) color_on(is_data ? color_data : color_rem); + while(program[pos] != 0x9b) { + c = program[pos++]; + if(color && is_data && c == ',') { + color_on(color_op); + comma = 1; + } else { + comma = 0; + } + print_ata_chr(c); + if(comma) + color_on(is_data ? color_data : color_rem); + } + if(inv) end_inv(0); + if(color) color_off(); } CALLBACK(print_newline) { - outchr(0x9b); + if(output_mode == M_ATASCII) + outchr(0x9b); + else + outchr('\n'); } CALLBACK(code_prot) { fprintf(stderr, "%s: Program is code-protected, stopping at line %d.\n", self, lineno); - close_outfh(); 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(); @@ -207,7 +1147,11 @@ int main(int argc, char **argv) { setup_outfh(); list(); - close_outfh(); + + if(badtok) { + fprintf(stderr, "%s: Program has unknown tokens; wrong BASIC dialect?\n", self); + return 2; + } return 0; } |