aboutsummaryrefslogtreecommitdiff
path: root/whichbas.c
blob: c407c333de41e57e4bd171caba90b3dc87f996b5 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
#include <stdio.h>
#include <unistd.h>
#include <stdlib.h>
#include <string.h>
#include <ctype.h>
#include <time.h>

#include "bas.h"

#define BT_ATARI 1
#define BT_TURBO 2
#define BT_BXL 4
#define BT_BXE 8

#define BT_BXL_BXE (BT_BXL | BT_BXE)

int bas_type = 0x0f; /* start out with all enabled */

int comma_count; /* count of regular commas (not string/array) in statement */
unsigned char last_cmd;
unsigned short last_cmd_pos;

void print_help(void) {
	printf("Usage: %s [-v] <inputfile>\n", self);
}

void parse_args(int argc, char **argv) {
	int opt;

	while( (opt = getopt(argc, argv, "v")) != -1) {
		switch(opt) {
			case 'v': verbose = 1; break;
			default:
				print_help();
				exit(1);
		}
	}

	if(optind >= argc)
		die("No input file given (and stdin not supported).");
	else
		open_input(argv[optind]);
	if(input_file == stdin)
		die("Reading from standard input not supported.");
}

/* don't need this.
void add_type(int type) {
	bas_type |= type;
}
*/

void remove_type(int type) {
	bas_type &= ((~type) & 0x0f);
}

void print_result(void) {
	const char *name;

	if(bas_type & BT_ATARI) {
		name = "Atari BASIC";
	} else if(bas_type == BT_BXL || bas_type == (BT_BXL | BT_BXE)) {
		name = "OSS BASIC XL";
	} else if(bas_type == BT_BXE) {
		name = "OSS BASIC XE";
	} else if(bas_type == BT_TURBO) {
		name = "Turbo BASIC XL";
	} else {
		name = "Not Atari BASIC; probably either Turbo or BXL/BXE";
	}

	fputs(name, stdout);
	putchar('\n');

	exit(bas_type == BT_ATARI ? 0 : bas_type + 8);
}

CALLBACK(handle_cmd) {
	int has_args = 0;
	unsigned char nexttok;

	last_cmd = tok;
	last_cmd_pos = pos;
	comma_count = 0;

	if(verbose) fprintf(stderr, "handle_cmd: lineno %d, tok $%02x, bas_type was %02x...", lineno, tok, bas_type);

	if(tok <= CMD_ERROR) return; /* legal in BASIC, ignore */
	remove_type(BT_ATARI);
	if(tok >= 0x5b) remove_type(BT_BXL);

	nexttok = program[pos + 1];
	has_args = !(nexttok == OP_EOS || nexttok == OP_EOL);

	/* we have tokens 0x3a to 0x68 in both TB and BXE, or 47
	   of them.
      Some tokens can't be determined, because they take the
      same argument (or lack of) in both Turbo and BXL/XE. These
      are:
      0x3c: REPEAT or ELSE (no args either way)
      0x42: Maybe: BPUT or RGET (take the same args... but not quite!)
      0x43: Maybe: BGET or BPUT (take the same args... but not quite!)
      0x46: LOOP or CP (no args either way)
      0x49: LOCK or UNPROTECT (take the same args)
      0x4B: RENAME in both Turbo and BXL/XE (take the same args)
      0x60: CLS or HITCLR (no args either way)
		This leaves 40 we can check.
		Covered so far: 34 (85%)
      TODO: Unknown tokens:
      0x54: ??? in TB (find out what), LVAR in BXL/BXE.
      0x5A: BLOAD or... what? (Jindroush lists it as ?5A?)
      TODO:
      0x5B: BRUN or CALL (both take a string, CALL allows "USING" though)
      0x5C: GO# (1 arg only) or SORTUP (optional 2nd arg of USING, but no comma)
      0x5D: # (1 arg only) or SORTDOWN (optional 2nd arg of USING, but no comma)
      0x5F: PAINT (req 2 args) or NUM (optional 2 args, probly not appear in program)
	 */
	switch(tok) {
		case 0x39: /* MOVE <args> or ENDWHILE */
		case 0x3a: /* -MOVE <args> or TRACEOFF */
		case 0x3d: /* UNTIL <args> or ENDIF */
		case 0x56: /* DEL <args> or FAST */
      case 0x61: /* DSOUND (4 num args) or INVERSE (no args) */
      case 0x62: /* CIRCLE (3 num args) or NORMAL (no args) */
			if(has_args) {
				remove_type(BT_BXL_BXE);
			} else {
				remove_type(BT_TURBO);
			}
			break;
      case 0x58: /* TRACE (optional + or -), EXTEND (BXE; no args) */
			/* EXTEND can't show up in a program except maybe line 32768, e.g.
			   EXTEND:SAVE "D:BLAH". */
			remove_type(BT_BXL);
			if(lineno < 32768) {
				remove_type(BT_BXE);
			}
			break;
      case 0x59: /* TEXT (1st arg is number), PROCEDURE (arg is string const (not var!)) */
			if(nexttok == OP_STRCONST) {
				remove_type(BT_TURBO);
			} else {
				remove_type(BT_BXL_BXE);
			}
			break;
		case 0x3f: /* WEND or LOMEM <args> */
		case 0x40: /* ELSE or DEL <args> */
		case 0x41: /* ENDIF or RPUT <args> */
		case 0x45: /* DO or TAB <args> */
		case 0x47: /* EXIT or ERASE <args> */
		case 0x51: /* ENDPROC or PMMOVE <args> */
			if(has_args) {
				remove_type(BT_TURBO);
			} else {
				remove_type(BT_BXL_BXE);
			}
			break;
      case 0x48: /* DIR (optional arg) or PROTECT (req'd arg) */
			/* not conclusive: without args means TB, but with arg,
				it could be either */
			if(!has_args) {
				remove_type(BT_BXL_BXE);
			}
			break;
      case 0x4a: /* UNLOCK (req'd arg) or DIR (optional arg) */
			/* not conclusive: without args means TB, but with arg,
				it could be either */
			if(!has_args) {
				remove_type(BT_TURBO);
			}
			break;
		case 0x3b: /* *F (optional + or -), TRACE (no arg) */
      case 0x5e: /* *B (optional + or -) or EXIT (no arg) */
			if(has_args) {
				remove_type(BT_BXL_BXE);
			}
			break;
      case 0x44: /* FILLTO or BGET (check for a # after the token) */
			if(nexttok == OP_HASH) {
				remove_type(BT_TURBO);
			} else {
				remove_type(BT_BXL_BXE);
			}
			break;
      case 0x4e: /* TIME$= (1 string arg) or PMCLR (1 num arg) */
			/* XXX: this doesn't do anything if the arg is a variable; we
			   could examine the type, but we don't yet */
			if(nexttok == OP_STRCONST) {
				remove_type(BT_BXL_BXE);
			} else if(nexttok == OP_NUMCONST) {
				remove_type(BT_TURBO);
			}
			break;
      case 0x50: /* EXEC (1 arg, *must* be variable) or PMGRAPHICS (1 num arg, may be const) */
			if(nexttok < 0x80) {
				remove_type(BT_TURBO);
			}
			break;
      case 0x57: /* DUMP (1 optional string arg) or LOCAL (1 variable arg) */
			if(!has_args || (nexttok == OP_STRCONST)) {
				/* if there's no arg, or one string constant arg... */
				/* XXX: DUMP A$ not detected */
				remove_type(BT_BXL_BXE);
			}
		default: break;
	}
	if(verbose) fprintf(stderr, " now %02x\n", bas_type);
}

CALLBACK(handle_op) {
	unsigned char nexttok  = program[pos + 1];
	unsigned char nexttok2 = program[pos + 2];

	if(tok == OP_COMMA) comma_count++;

	if(verbose) fprintf(stderr, "handle_op: lineno %d, tok $%02x, comma_count %d, bas_type was %02x...", lineno, tok, comma_count, bas_type);

	if(tok == 0x0d) remove_type(BT_ATARI); /* hex const (turbo *and* bxl/xe) */
	if(tok <= OP_FUNC_STRIG) {
		if(verbose) fprintf(stderr, " now %02x\n", bas_type);
		return; /* legal in BASIC, ignore */
	}
	remove_type(BT_ATARI);

	if(tok >= 0x69) {
		remove_type(BT_BXL_BXE);
	}

	if(tok == 0x55) {
		/* DPEEK (function) TB, USING (infix, not a function) in BXL/BXE */
		if(nexttok == OP_FUNC_LPAR) {
			remove_type(BT_BXL_BXE);
		}
	}

	if(tok == 0x5c) {
		/* DEC (function, takes str) in TB, HEX$ (function, takes num) in BXL/BXE */
		if(nexttok2 == OP_STRCONST) {
			remove_type(BT_BXL_BXE);
		} else if(nexttok2 > 0x80 && (get_vartype(nexttok2) == TYPE_STRING)) {
			/* TODO: see if this test is actually valid! */
			remove_type(BT_BXL_BXE);
		}
	}

	if(tok == 0x5f) {
		/* TIME$ in TB, SYS (function) in BXL/BXE */
		if(nexttok == OP_FUNC_LPAR) {
			remove_type(BT_TURBO);
		}
	}

	if(tok == 0x60) {
		/* TIME in TB, VSTICK (function) in BXL/BXE */
		if(nexttok == OP_FUNC_LPAR) {
			remove_type(BT_TURBO);
		}
	}

	if(tok == 0x61) {
		/* MOD (infix op) in TB, HSTICK (function) in BXL/BXE */
		if(nexttok == OP_FUNC_LPAR)
			remove_type(BT_TURBO);
	}

	if(tok == 0x62) {
		/* EXEC (infix op, with ON) in TB, PMADR (function) in BXL/BXE */
		if(nexttok == OP_FUNC_LPAR)
			remove_type(BT_TURBO);
	}

	if(tok == 0x66 || tok == 0x67 || tok == 0x68) {
		/* either %0 %1 %2 (TB), or LEFT$ RIGHT$ MID$ (BXL/XE) */
		if(nexttok == OP_STRCONST || nexttok >= 0x80) {
			/* %0 %1 %2 can't be followed by a string constant *or* a variable */
			remove_type(BT_TURBO);
		}
	}
	if(verbose) fprintf(stderr, " now %02x\n", bas_type);
}

CALLBACK(handle_end_stmt) {
	if(verbose) fprintf(stderr, "handle_end_stmt: lineno %d, tok $%02x, last_cmd $%02x, comma_count %d, bas_type was %02x...", lineno, tok, last_cmd, comma_count, bas_type);
	switch(last_cmd) {
		case 0x38: /* DPOKE (2 args) or WHILE (1 arg) */
			if(comma_count) {
				remove_type(BT_BXL_BXE);
			} else {
				remove_type(BT_TURBO);
			}
			break;
		case 0x3e: /* WHILE (1 arg) or DPOKE (2 args) */
		case 0x4c: /* DELETE (1 arg) or MOVE (3 or 4 args) */
		case 0x4d: /* PAUSE (1 arg) or MISSILE (3 args) */
      case 0x52: /* FCOLOR (1 arg) or PMWIDTH (2 args) */
      case 0x53: /* *L (optional + or - only) or SET (req 2 num args) */
      case 0x4f: /* PROC (1 arg) or PMCOLOR (3 args) */
			if(comma_count) {
				remove_type(BT_TURBO);
			} else {
				remove_type(BT_BXL_BXE);
			}
			break;
      case 0x55: /* RENUM in both (TB req 3 args, BXL up to two) */
			if(comma_count == 2) {
				remove_type(BT_BXL_BXE);
			} else {
				remove_type(BT_TURBO);
			}
			break;
      case 0x63: /* %PUT (usually seen with optional #; 1 or 2 args) or BLOAD (1 string arg) */
			if(comma_count) {
				/* multiple args */
				remove_type(BT_BXL_BXE);
			} else if(program[last_cmd + 1] == OP_STRCONST) {
				/* one arg, string const. XXX: check var type */
				remove_type(BT_TURBO);
			}
			break;
      case 0x64: /* %GET (usually seen with optional #; 1 or 2 args) or BSAVE (3 args) */
			if(comma_count == 2) {
				remove_type(BT_TURBO);
			} else {
				remove_type(BT_BXL_BXE);
			}
			break;
		default: break;
	}
	if(verbose) fprintf(stderr, " now %02x\n", bas_type);
}

void foreign(const char *name) {
	fclose(input_file);
	puts(name);
	exit(0); /* TODO: pick a better number */
}

void detect_foreign(void) {
	int i, nuls, c, d;

	c = fgetc(input_file);
	d = fgetc(input_file);

	if(c == 0 && d == 0) {
		/* This is why we can't read from stdin. */
		rewind(input_file);
		return;
	}

	if(c == EOF || d == EOF)
		die("File is too short to be a BASIC program of any kind.");

	if(c == 0xff && d == 0xff)
		foreign("XEX executable (not BASIC at all!)");

	if(c == 0xfe && d == 0xfe)
		foreign("Mac/65 tokenized source (not BASIC at all!)");

	if(c == 0xdd && d == 0x00)
		foreign("EXTENDed OSS BASIC XE");

	if(c == 0x7f && d == 'E') {
		c = fgetc(input_file);
		d = fgetc(input_file);
		if(c == 'L' && d == 'F')
			foreign("ELF executable (huh?)");
	}

	if(!(c == 0 && d == 0)) {
		if(fseek(input_file, -3, SEEK_END) == 0) {
			nuls = 0;
			for(i = 0; i < 3; i++) {
				if(fgetc(input_file) == 0) nuls++;
			}
			if(nuls == 3) {
				foreign("Microsoft BASIC");
			}
		}
	}

	if(isdigit(c) && (d == 0x20 || isdigit(d)))
		foreign("Text file, could be LISTed BASIC (or not)");

	if(isprint(c) && isprint(d))
		foreign("Text file (not BASIC at all!)");

	foreign("Unknown file type (not BASIC at all!)");
}

int main(int argc, char **argv) {
	set_self(*argv);
	parse_general_args(argc, argv, print_help);
	parse_args(argc, argv);

	detect_foreign();

	readfile();
	parse_header();

	on_cmd_token = handle_cmd;
	on_exp_token = handle_op;
	on_end_stmt = handle_end_stmt;

	walk_all_code();

	print_result(); /* always exits */
	return 0; /* never happens, shuts up gcc's warning though */
}