From ce201121b5785d9ecb2829d262c23050e537fa9e Mon Sep 17 00:00:00 2001 From: "B. Watson" Date: Thu, 13 Jun 2024 15:32:22 -0400 Subject: add renumbas. --- renumbas.c | 403 +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 403 insertions(+) create mode 100644 renumbas.c (limited to 'renumbas.c') diff --git a/renumbas.c b/renumbas.c new file mode 100644 index 0000000..272b832 --- /dev/null +++ b/renumbas.c @@ -0,0 +1,403 @@ +#include +#include +#include +#include +#include +#include + +#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] \n", self); + fprintf(stderr, " -v: Verbose.\n"); + fprintf(stderr, " -s : Starting line number (default: 10).\n"); + fprintf(stderr, " -i : Increment (default: 10).\n"); + fprintf(stderr, " -f : Don't renumber lines less than (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; +} -- cgit v1.2.3