#include #include #include #include #include #include #include #include #include #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 */ 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 */ /* 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 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) != 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 print_help(void) { printf("Usage: %s [-a|-d|-m|-x|-U] [-B] [-i] [-l] [-u] [-n|-C] [-v] [-c *colors*] \n", self); printf(" -b : 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(" -v: verbose.\n"); } void parse_args(int argc, char **argv, int from_env) { int opt; optind = 1; while( (opt = getopt(argc, argv, "Db:UCviamnBdhxulc:")) != -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 '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 'b': autodetect = 0; bas_type = get_bas_type(optarg); 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 { 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(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(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) { 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_mixed_case(const char *name) { char c; if(*name == ' ') outchr(*name++); outchr(*name++); while(( c = *name++)) { if(c >= 'A' && c <= 'Z') c |= 0x20; if(color && c == '(') color_on(color_op); outchr(c); } } void print_string(unsigned int pos, unsigned int len) { inv = 0; 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(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)"; } } CALLBACK(print_lineno) { if(skip_lineno) return; 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((!(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(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); /* should never get here actually */ } CALLBACK(print_op) { const char *name; switch(tok) { case OP_HEXCONST: if(color) color_on(color_op); outchr('$'); if(color) color_off(); /* fall thru */ 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) op_color_on(tok); if((!(name = op_tokens[tok]))) { fprintf(outfh, "(bad op token $%02x)", tok); 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; if(color) color_on(color_varnames); tok &= 0x7f; for(i = vnstart, count = 0; count < tok; i++) { if(program[i] & 0x80) count++; if(i == codestart) { fprintf(outfh, "(bad var tok $%02x)", tok | 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_text) { 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) { 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); } 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_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; } int main(int argc, char **argv) { set_self(*argv); parse_general_args(argc, argv, print_help); parse_env_args(); parse_args(argc, argv, 0); init_bas_dialect(); init_token_tables(); 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; wrong BASIC dialect?\n", self); return 2; } return 0; }