#include #include #include #include #include #include #include #include #include "bas.h" #include "bcdfp.h" #include "tokens.h" #include "atables.h" #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. don't use blue, it's fugly. */ #define NO_COLOR -1 #define C_RED 1 #define C_GREEN 2 #define C_YELLOW 3 #define C_PURPLE 5 #define C_CYAN 6 #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 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 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_text = C_CYAN; int color_varnames = NO_COLOR; 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 */ 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) != 7) die("Color scheme must be 7 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_text = parse_color(arg[5]); color_varnames = parse_color(arg[6]); } void print_help(void) { printf("Usage: %s [-a | -d | -m | -x] [-b] [-i] [-n] [-v] [-c *colors*] \n", self); 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(" -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(" -v: verbose.\n"); } void parse_args(int argc, char **argv, int from_env) { int opt; optind = 1; while( (opt = getopt(argc, argv, "UCviamnbdhxuc:")) != -1) { switch(opt) { 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 '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 'c': parse_color_scheme(optarg); break; case 'h': print_help(); exit(0); default: print_help(); exit(1); } } if(!from_env) { if(optind >= argc) die("No input file given (use - for stdin)."); else open_input(argv[optind]); } } /* 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(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) { putc(c, outfh); } /* this should probably be moved to bcdfp.c */ double bcd2double(const unsigned char *num) { double result = 0, sign; int exp, i; exp = *num; if(!exp) { return 0.0; } sign = (exp & 0x80 ? -1.0 : 1.0); exp &= 0x7f; exp -= 0x40; for(i = 1; i < 6; i++) { result *= 100.0; result += bcd2int(num[i]); } result *= pow(100, exp - 4); result *= sign; return result; } void color_on(unsigned char 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) { if(color) color_on(color_const); fprintf(outfh, "%G", bcd2double(program + pos)); 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_string(unsigned int pos, unsigned int len) { inv = 0; outchr('"'); if(color) color_on(color_const); while(len--) print_ata_chr(program[pos++]); if(inv) { end_inv(0); } if(color) color_off(); outchr('"'); } CALLBACK(print_lineno) { if(color) color_on(color_lineno); fprintf(outfh, "%d ", lineno); if(color) color_off(); } CALLBACK(print_cmd) { const char *name; if(tok == CMD_ILET) return; if(color) color_on(color_cmd); if(tok > last_command || (!(name = commands[tok]))) { fprintf(outfh, "(bad cmd token $%02x) ", tok); badtok = 1; } else { fprintf(outfh, "%s ", name); } if(color) color_off(); } CALLBACK(print_op) { const char *name; switch(tok) { case OP_NUMCONST: print_number(pos + 1); return; case OP_STRCONST: print_string(pos + 2, program[pos + 1]); return; case OP_EOL: return; default: break; } if(color) { 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); } if(tok > last_operator || (!(name = operators[tok]))) { fprintf(outfh, "(bad op token $%02x)", tok); badtok = 1; } else { fprintf(outfh, "%s", name); } if(color) color_off(); } CALLBACK(print_varname) { int i, count; unsigned char c; if(color) color_on(color_varnames); tok &= 0x7f; for(i = vnstart, count = 0; count < tok; i++) { if(program[i] & 0x80) count++; } 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_text) { unsigned char c, is_data = program[pos - 1] == CMD_DATA, comma = 0; inv = 0; if(color) color_on(color_text); while(program[pos] != 0x9b) { c = program[pos++]; if(color && is_data && c == ',') { color_off(); comma = 1; } else { comma = 0; } print_ata_chr(c); if(comma) color_on(color_text); } if(inv) end_inv(0); if(color) color_off(); } CALLBACK(print_newline) { 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); exit(0); } void list(void) { on_start_line = print_lineno; on_cmd_token = print_cmd; on_exp_token = print_op; on_var_token = print_varname; on_end_line = print_newline; on_text = print_text; on_bad_line_length = code_prot; walk_code(0, 32767 + immediate); } int main(int argc, char **argv) { set_self(*argv); parse_general_args(argc, argv, print_help); parse_env_args(); parse_args(argc, argv, 0); readfile(); parse_header(); if(!vntable_ok()) die("Program is variable-protected; unprotect it first."); setup_outfh(); list(); if(badtok) { fprintf(stderr, "%s: Program has unknown tokens; maybe Turbo BASIC or BASIC XL/XE?\n", self); return 2; } return 0; }