diff options
Diffstat (limited to 'listbas.c')
| -rw-r--r-- | listbas.c | 1116 | 
1 files changed, 1010 insertions, 106 deletions
@@ -8,45 +8,239 @@  #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 "int_tokens.h" +#include "int_disk_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 B_INT_C SRET_OSSINT_CART +#define B_INT_D SRET_OSSINT_DISK + +#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] == 'i') { +		if(arg[1] == 'd') +			return B_INT_D; +		else if(arg[1] == 'c') +			return B_INT_C; +	} + +	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 (start > end)."); +} +  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:\n"); +	printf("     a  = Atari 8K   t  = Turbo    xl = Basic XL    xe = Basic XE\n"); +	printf("     a+ = OSS A+     ic = OSS Integer (cart)        id = OSS Int (disk)\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 +248,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,20 +348,49 @@ 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_int_number(unsigned int pos, int hex) { +	unsigned int num = program[pos] | (program[pos + 1] << 8); +	if(color) color_on(color_const); +	if(hex) { +		fprintf(outfh, (num > 0xff ? "%04x" : "%02x"), num); +	} else { +		fprintf(outfh, "%d", num); +	} +	if(color) color_off(); +} + +void print_bcd_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();  } +void print_number(unsigned int pos, int hex) { +	if(bas_type == B_INT_C || bas_type == B_INT_D) +		print_int_number(pos, hex); +	else +		print_bcd_number(pos, hex); +} +  /* 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} @@ -120,22 +398,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 +421,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,119 +476,537 @@ 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; +		/* TODO: case B_INT_C: int_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(bas_type == B_INT_C) { +		if(tok == 0x37) return; +	} else if(bas_type == B_INT_D) { +		if(tok == 0x35) 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();  } -CALLBACK(print_op) { -	const char *name; +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 OP_NUMCONST: -			print_number(pos + 1); -			return; -		case OP_STRCONST: -			print_string(pos + 2, program[pos + 1]); -			return; -		case OP_EOL: -			return; -		default: break; +		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;  	} -	if(color) { +	/* common ops for Atari, Turbo, BXL, BXE */ +	if(tok <= last_operator) {  		if(tok > 0x3c) -			color_on(C_PURPLE); -		else if(tok == OP_UMINUS) -			color_on(C_RED); /* show leading - in same color as the number */ +			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(C_YELLOW); +			color_on(color_cmd);  		else -			color_on(C_GREEN); +			color_on(color_op); +		return;  	} -	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(); + +	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 */  } -CALLBACK(print_varname) { +void print_varname(unsigned char varnum) {  	int i, count; -	unsigned char c, c_on = 0;; +	unsigned char c; + +	if(color) color_on(color_varnames); -	tok &= 0x7f; -	for(i = vnstart, count = 0; count < tok; i++) { +	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)) { -			color_on(C_GREEN); -			c_on = 1; +			if(color) color_on(color_op);  		}  		outchr(c & 0x7f);  	} while (c < 0x80); -	if(c_on) color_off(); +	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, 0); +			return; +		case OP_STRCONST: +			print_string(pos + 2, program[pos + 1]); +			return; +		case OP_EOL: +			/* in Integer BASIC, this token is TO */ +			if((bas_type != B_INT_C) && (bas_type != B_INT_D)) return; +			break; +		case 0x13: +			/* in Integer BASIC, this is the real end-of-line token */ +			if(bas_type == B_INT_D || bas_type == B_INT_C) 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 || bas_type == B_INT_C || bas_type == B_INT_D) +			print_mixed_case(name); +		else +			fprintf(outfh, "%s", name); +	} +	if(color) 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 +1014,207 @@ 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); +} + +/* cartridge version */ +void init_int_tables() { +	memmove(cmd_tokens, int_cmds, int_cmd_size); +	memmove(op_tokens, int_ops, int_ops_size); +} + +void init_int_disk_tables() { +	memmove(cmd_tokens, int_disk_cmds, int_disk_cmd_size); +	memmove(op_tokens, int_disk_ops, int_disk_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; +	} else if(bas_type == B_INT_C) { +		init_int_tables(); +		return; +	} else if(bas_type == B_INT_D) { +		init_int_disk_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: +		case SRET_OSSINT_CART: +		case SRET_OSSINT_DISK: +			bas_type = d; +			break; +		case SRET_AMSB: +			die("This is an Atari Microsoft BASIC program. Use listamsb."); +			break; +		case SRET_COMPILED_TURBO: +		case SRET_NOT_BASIC: +			die("This is not any kind of BASIC program I know about."); +			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_INT_C || bas_type == B_INT_D) { +		numconst_size = 2; +		if(bas_type == B_INT_C) +			error_token = 0x38; +		else +			error_token = 0x36; +		allow_hex_const = 1; +		mixed_case = 1; +	} + +	if(bas_type == B_TURBO || bas_type == B_BXL || bas_type == B_BXE) +		allow_hex_const = 1; + +	if(bas_type == B_APLUS) +		error_token = 0x53; + +	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 +1226,7 @@ int main(int argc, char **argv) {  	list();  	if(badtok) { -		fprintf(stderr, "%s: program has unknown tokens; maybe Turbo BASIC or BASIC XL?", self); +		fprintf(stderr, "%s: Program has unknown tokens; wrong BASIC dialect?\n", self);  		return 2;  	}  | 
