aboutsummaryrefslogtreecommitdiff
path: root/renumbas.c
blob: 272b83285ffad43a94ac185014c9b2c0e1a1206b (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
#include <stdio.h>
#include <unistd.h>
#include <stdlib.h>
#include <string.h>
#include <ctype.h>
#include <time.h>

#include "bas.h"

/* remove/comment to turn off debug printing */
#define DEBUG

#ifdef DEBUG
#  define IFDEBUG(x) x
#else
#  define IFDEBUG(x)
#endif

int startlineno = 10;
int increment   = 10;
int limit       = 0;

unsigned short *linerefs[32768];
int linerefcounts[32768];

void print_help(void) {
	fprintf(stderr, "Usage: %s [-v] [-s start-lineno] [-i increment] [-f first-lineno] <inputfile> <outputfile>\n", self);
	fprintf(stderr, "        -v: Verbose.\n");
	fprintf(stderr, "  -s <num>: Starting line number (default: 10).\n");
	fprintf(stderr, "  -i <num>: Increment (default: 10).\n");
	fprintf(stderr, "  -f <num>: Don't renumber lines less than <num> (default: 0).\n");
}

unsigned short getlineno(char opt, const char *arg) {
	int lineno;
	char *e;

	lineno = (int)strtol(arg, &e, 10);

	if(*e) {
		fprintf(stderr, "%s: Invalid line number for -%c option: %s is not a number.\n",
				self, opt, arg);
		exit(1);
	}

	if(lineno < 0 || lineno > 32767) {
		fprintf(stderr, "%s: Invalid line number for -%c option: %d > 32767.\n",
				self, opt, lineno);
		exit(1);
	}

	return ((unsigned short)lineno);
}

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

	while( (opt = getopt(argc, argv, "vs:i:f:")) != -1) {
		switch(opt) {
			case 'v': verbose = 1; break;
			case 's': startlineno = getlineno(opt, optarg); break;
			case 'i': increment   = getlineno(opt, optarg); break;
			case 'f': limit       = getlineno(opt, optarg); break;
			default:
				print_help();
				exit(1);
		}
	}

	if(optind >= argc)
		die("No input file given (use - for stdin).");
	else
		open_input(argv[optind]);

	if(++optind >= argc)
		die("No output file given (use - for stdout).");
	else
		output_filename = argv[optind];
}

unsigned char bcd2int(unsigned char bcd) {
	return (bcd >> 4) * 10 + (bcd & 0x0f);
}

unsigned char int2bcd(unsigned char i) {
	return ((i / 10) << 4) | (i % 10);
}

unsigned short fp2int(const unsigned char *fp) {
	unsigned short result = 0;

	/* examine the exponent/sign byte */
	if(fp[0] == 0) return 0; /* special case */
	if(fp[0] & 0x80) die("negative numbers not supported");

	switch(fp[0]) {
		case 0x40:
			result = bcd2int(fp[1]); break;
		case 0x41:
			result = bcd2int(fp[1]) * 100 + bcd2int(fp[2]); break;
		case 0x42:
			result = bcd2int(fp[1]) * 10000 + bcd2int(fp[2]) * 100 + bcd2int(fp[3]); break;
		default:
			die("number out of range"); break;
	}

	return result;
}

void int2fp(unsigned short num, unsigned char *fp) {
	memset(fp, 0, 6);

	if(num == 0) return;

	if(num >= 10000) {
		fp[0] = 0x42;
		fp[3] = int2bcd(num % 100);
		num /= 100;
		fp[2] = int2bcd(num % 100);
		num /= 100;
		fp[1] = int2bcd(num);
	} else if(num >= 100) {
		fp[0] = 0x41;
		fp[2] = int2bcd(num % 100);
		num /= 100;
		fp[1] = int2bcd(num);
	} else {
		fp[0] = 0x40;
		fp[1] = int2bcd(num);
	}
}

void addlineref(unsigned short refaddr) {
	int target = fp2int(program + refaddr);
	unsigned short *p = linerefs[target];
	int c = linerefcounts[target];

	IFDEBUG(printf("addlineref: target=%d, 0x%04x\n", target, refaddr));

	if(c) {
		p = realloc(p, sizeof(unsigned short) * (c + 1));
	} else {
		p = malloc(sizeof(unsigned short));
	}

	if(!p) die("Out of memory.");

	linerefs[target] = p;
	p[c] = refaddr;
	c++;
	linerefcounts[target] = c;
}

void printlinerefs(void) {
	int i, j;
	printf("linerefs:\n");
	for(i = 0; i < 32768; i++) {
		if(linerefcounts[i]) {
			printf("%d: ", i);
			for(j = 0; j < linerefcounts[i]; j++) {
				printf("%04x ", linerefs[i][j]);
			}
			putchar('\n');
		}
	}
}

void freelinerefs(void) {
	int i;
	for(i = 0; i < 32768; i++) {
		if(linerefcounts[i]) {
			free(linerefs[i]);
		}
	}
}

/* tokens that can take line numbers:
	Commands:
	GOTO   0x0a
	GO TO  0x0b
	GOSUB  0x0c
	TRAP   0x0d
	LIST   0x04 (but don't bother)
	RESTORE 0x23

	Operators:
	GOTO   0x17 (as in, ON (0x1e) GOTO)
	GOSUB  0x18 (ON = 0x1e again)
	THEN   0x1b (but not really!)

	beware: e.g. GOTO 1000+A should not have the 1000 changed.

	numeric constant introduced with 0x0e, followed by 6 BCD bytes.
	string constant 0x0f, length byte, then (length) bytes.
*/

int is_xfer_cmd(unsigned char tok) {
	int ret;
	switch(tok) {
		case CMD_GOTO:
		case CMD_GO_TO:
		case CMD_GOSUB:
		case CMD_TRAP:
		case CMD_LIST:
		case CMD_RESTORE:
			ret = 1; break;
		default:
			ret = 0; break;
	}
	IFDEBUG(printf("is_xfer_cmd(%02x) == %d\n", tok, ret));
	return ret;
}

int skip_op_token(int pos) {
	switch(program[pos]) {
		case OP_EOS:
			return pos + 2; /* skip next-statement offset */
		case OP_EOL:
			return pos + 3; /* skip 2-byte line number */
		case OP_NUMCONST:
			return pos + 7; /* skip 6-byte BCD float */
		case OP_STRCONST:
			return pos + 2 + program[pos + 1]; /* 2nd byte is string len */
		default:
			return pos + 1;
	}
}

/* ON/GOTO and ON/GOSUB can have any number of arguments, separated
   by OP_COMMA. *Normally* these are simple FP constants, since the
   whole point of ON is to avoid computed line numbers... but they're
   allowed to be expressions so we have to check. */
int handle_on_goto(int lineno, int pos) {
	unsigned char tok, nexttok, main_tok;

	IFDEBUG(printf("handle_on_goto(%02x)\n", pos));

	main_tok = program[pos]; /* save this, for use in 'computed' warning */

	pos++; /* skip GOTO/GOSUB token */

	while(1) {
		tok = program[pos];
		if(tok == OP_EOS || tok == OP_EOL)
			break;
		if(tok == OP_COMMA) {
			pos++;
			continue;
		}
		nexttok = program[pos + 7];
		if(tok != OP_NUMCONST || !(nexttok == OP_COMMA || nexttok == OP_EOS || nexttok == OP_EOL)) {
			fprintf(stderr, "Computed line number in ON/%s at line %d.\n",
					(main_tok == OP_GOTO ? "GOTO" : "GOSUB"), lineno);
			break;
		}
		addlineref(pos + 1);
		pos += 7;
	}

	return pos;
}

/* IF/THEN can be followed by a simple line number (not a full expression)
   or a statement offset (*without* an OP_EOS token!) followed by a
   statement.
   The right way to do this would be to track the statement offsets, but
   this works fine. It relies on the fact that line numbers always have
   an exponent byte of 0x40 to 0x42, and the fact that 0x40 to 0x42 are
   not valid command tokens.
 */
int handle_then(int pos) {
	unsigned char tok1 = program[pos + 1];
	unsigned char tok2 = program[pos + 2];
	if(tok1 == OP_NUMCONST && (tok2 >= 0x40 && tok2 <= 0x42)) {
		addlineref(pos + 2);
		return pos + 7;
	} else {
		return 0;
	}
}

void renumber(void) {
	int pos = codestart, nextpos;
	int lineno, offset;
	int newno, i;
	unsigned char tok;
	unsigned char fpnewno[6];

	/* pass 1: find references to line numbers, flag them. */
	while(pos < filelen) {
		lineno = getword(pos);
		if(lineno == 32768) break;
		offset = program[pos + 2];
		IFDEBUG(printf("checking line %d, pos %04x...\n", lineno, pos));
		if(offset < 6)
			die("Can't renumber a code-protected program, unprotect it first.");
		nextpos = pos + offset;

		pos += 4; /* skip line number, line length, 1st statement length */

		/* loop over the statements in this line */
		while(pos < nextpos) {
			/* every statement starts with a command token */
			tok = program[pos];

			if(tok == CMD_REM || tok == CMD_DATA || tok == CMD_ERROR)
				break; /* ignore rest of line */

			if(is_xfer_cmd(tok)) {
				unsigned char nexttok = program[pos + 8];
				if(program[pos + 1] == OP_NUMCONST && (nexttok == OP_EOS || nexttok == OP_EOL)) {
					addlineref(pos + 2);
				} else {
					fprintf(stderr, "Computed line number at line %d.\n", lineno);
				}
			}

			pos++;

			/* rest of statement is expressions/operators */
			while(pos < nextpos) {
				tok = program[pos];

				if(tok == OP_EOL) break;

				if(tok == OP_EOS) {
					pos += 2; /* end statement, skip statement length of next one */
					break;
				}

				if(tok == OP_GOTO || tok == OP_GOSUB) {
					pos = handle_on_goto(lineno, pos);
					continue;
				} else if(tok == OP_THEN) {
					i = handle_then(pos);
					if(i) {
						pos = i;
						continue;
					} else {
						pos += 2; /* skip statement length */
						break;
					}
				}

				IFDEBUG(printf("tok is %02x, pos was %02x before skip_op_token... ", tok, pos));
				pos = skip_op_token(pos);
				IFDEBUG(printf("pos now %02x\n", pos));
			}
		}

		pos = nextpos; /* point to next line */
	}

	IFDEBUG(printlinerefs());

	/* pass 2: renumber the lines, and update the references in other lines */
	newno = startlineno;
	pos = codestart;
	while(pos < filelen) {
		if(newno >= 32768) {
			fprintf(stderr, "New line number %d out of range, renumber failed.", newno);
			exit(1);
		}

		lineno = getword(pos);
		offset = program[pos + 2];

		if(lineno >= limit) {
			if(lineno == 32768) break;

			IFDEBUG(printf("renumbering line %d as %d, %d refs\n", lineno, newno, linerefcounts[lineno]));

			/* update refs to old line number with new one */
			int2fp(newno, fpnewno);
			for(i = 0; i < linerefcounts[lineno]; i++)
				memmove(program + linerefs[lineno][i], fpnewno, 6);

			/* update the actual line number */
			setword(pos, newno);

			newno += increment;
		}
		pos += offset;
	}

	freelinerefs();
}

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

	readfile();
	parse_header();

	renumber();

	open_output(output_filename);
	writefile();

	return 0;
}