#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 C_RED 1 #define C_GREEN 2 #define C_YELLOW 3 #define C_PURPLE 5 #define C_CYAN 6 /* 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 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; void print_help(void) { printf("Usage: %s [-a | -d | -m | -x] [-b] [-i] [-n] [-v] \n", self); 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(" -n: disable color syntax highlighting.\n"); printf(" -n: use underline for inverse video.\n"); printf(" -v: verbose.\n"); } void parse_args(int argc, char **argv) { int opt; int opt_a = 0, opt_m = 0, opt_d = 0, opt_x = 0; while( (opt = getopt(argc, argv, "viamnbdhxu")) != -1) { switch(opt) { case 'v': verbose = 1; break; case 'i': immediate = 1; break; case 'a': opt_a = 1; break; case 'm': opt_m = 1; break; case 'd': opt_d = 1; break; case 'x': opt_x = 1; break; case 'b': bold = 1; break; case 'u': underline = 1; break; case 'n': color = 0; break; case 'h': print_help(); exit(0); default: print_help(); exit(1); } } if(opt_a + opt_d + opt_m + opt_x > 1) die("Only use one of the -a, -d, -m, -x options."); if(opt_a) { output_mode = M_ATASCII; } else if(opt_d) { output_mode = M_DOTS; } else if(opt_m) { output_mode = M_MAG; } else if(opt_x) { output_mode = M_UTF8_I; } if(optind >= argc) die("No input file given (use - for stdin)."); else open_input(argv[optind]); } void setup_outfh(void) { if(output_mode == M_ATASCII) { if(isatty(fileno(stdout))) { die("Refusing to write ATASCII data to the terminal."); } } /* TODO: reopen in binary mode! */ outfh = stdout; 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; 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(C_RED); 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: 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: 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(C_RED); while(len--) print_ata_chr(program[pos++]); if(inv) { end_inv(0); } if(color) color_off(); outchr('"'); } CALLBACK(print_lineno) { if(color) color_on(C_CYAN); fprintf(outfh, "%d ", lineno); if(color) color_off(); } CALLBACK(print_cmd) { const char *name; if(tok == CMD_ILET) return; if(color) color_on(C_YELLOW); 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(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]))) { 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, 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(); } CALLBACK(print_text) { unsigned char c, is_data = program[pos - 1] == CMD_DATA, comma = 0; inv = 0; if(color) color_on(C_CYAN); 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(C_CYAN); } 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_args(argc, argv); 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; }