From e2250e5cb71c9a06bf75d59e9c0369efd65b1206 Mon Sep 17 00:00:00 2001 From: "B. Watson" Date: Sun, 16 Jun 2024 17:33:22 -0400 Subject: cxrefbas: added. --- Makefile | 6 +- bas.h | 1 + bcdfp.c | 56 +++++++++++++++++ bcdfp.h | 4 ++ cxrefbas.1 | 103 +++++++++++++++++++++++++++++++ cxrefbas.c | 198 +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ cxrefbas.rst | 50 +++++++++++++++ vxrefbas.c | 2 +- 8 files changed, 417 insertions(+), 3 deletions(-) create mode 100644 bcdfp.c create mode 100644 bcdfp.h create mode 100644 cxrefbas.1 create mode 100644 cxrefbas.c create mode 100644 cxrefbas.rst diff --git a/Makefile b/Makefile index 8bd3ebf..d49b400 100644 --- a/Makefile +++ b/Makefile @@ -16,9 +16,9 @@ CC=gcc CFLAGS=-Wall $(COPT) -ansi -D_GNU_SOURCE -DVERSION=\"$(VERSION)\" # BINS and SCRIPTS go in $BINDIR, DOCS go in $DOCDIR -BINS=a8eol atr2xfd atrsize axe blob2c blob2xex cart2xex dumpbas fenders protbas renumbas rom2cart unmac65 unprotbas vxrefbas xex1to2 xexamine xexcat xexsplit xfd2atr +BINS=a8eol atr2xfd atrsize axe blob2c blob2xex cart2xex cxrefbas dumpbas fenders protbas renumbas rom2cart unmac65 unprotbas vxrefbas xex1to2 xexamine xexcat xexsplit xfd2atr SCRIPTS=dasm2atasm a8utf8 -MANS=a8eol.1 xfd2atr.1 atr2xfd.1 blob2c.1 cart2xex.1 fenders.1 xexsplit.1 xexcat.1 atrsize.1 rom2cart.1 unmac65.1 axe.1 dasm2atasm.1 a8utf8.1 blob2xex.1 xexamine.1 xex1to2.1 unprotbas.1 protbas.1 renumbas.1 dumpbas.1 vxrefbas.1 +MANS=a8eol.1 xfd2atr.1 atr2xfd.1 blob2c.1 cart2xex.1 fenders.1 xexsplit.1 xexcat.1 atrsize.1 rom2cart.1 unmac65.1 axe.1 dasm2atasm.1 a8utf8.1 blob2xex.1 xexamine.1 xex1to2.1 unprotbas.1 protbas.1 renumbas.1 dumpbas.1 vxrefbas.1 cxrefbas.1 MAN5S=xex.5 MAN7S=atascii.7 DOCS=README.txt equates.inc *.dasm LICENSE ksiders/atr.txt @@ -59,6 +59,8 @@ dumpbas: bas.o vxrefbas: bas.o +cxrefbas: bas.o bcdfp.o + bas.o: bas.c bas.h subdirs: diff --git a/bas.h b/bas.h index 1a3ee17..4e30431 100644 --- a/bas.h +++ b/bas.h @@ -35,6 +35,7 @@ *not* a command). */ #define CMD_GOTO 0x0a #define CMD_GO_TO 0x0b +#define CMD_ON 0x1e #define CMD_GOSUB 0x0c #define CMD_TRAP 0x0d #define CMD_LIST 0x04 diff --git a/bcdfp.c b/bcdfp.c new file mode 100644 index 0000000..76157c1 --- /dev/null +++ b/bcdfp.c @@ -0,0 +1,56 @@ +#include +#include "bcdfp.h" + +extern void die(const char *msg); + +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); + } +} diff --git a/bcdfp.h b/bcdfp.h new file mode 100644 index 0000000..a640122 --- /dev/null +++ b/bcdfp.h @@ -0,0 +1,4 @@ +unsigned char bcd2int(unsigned char bcd); +unsigned char int2bcd(unsigned char i); +unsigned short fp2int(const unsigned char *fp); +void int2fp(unsigned short num, unsigned char *fp); diff --git a/cxrefbas.1 b/cxrefbas.1 new file mode 100644 index 0000000..47909bd --- /dev/null +++ b/cxrefbas.1 @@ -0,0 +1,103 @@ +.\" Man page generated from reStructuredText. +. +. +.nr rst2man-indent-level 0 +. +.de1 rstReportMargin +\\$1 \\n[an-margin] +level \\n[rst2man-indent-level] +level margin: \\n[rst2man-indent\\n[rst2man-indent-level]] +- +\\n[rst2man-indent0] +\\n[rst2man-indent1] +\\n[rst2man-indent2] +.. +.de1 INDENT +.\" .rstReportMargin pre: +. RS \\$1 +. nr rst2man-indent\\n[rst2man-indent-level] \\n[an-margin] +. nr rst2man-indent-level +1 +.\" .rstReportMargin post: +.. +.de UNINDENT +. RE +.\" indent \\n[an-margin] +.\" old: \\n[rst2man-indent\\n[rst2man-indent-level]] +.nr rst2man-indent-level -1 +.\" new: \\n[rst2man-indent\\n[rst2man-indent-level]] +.in \\n[rst2man-indent\\n[rst2man-indent-level]]u +.. +.TH "CXREFBAS" 1 "2024-06-16" "0.2.1" "Urchlay's Atari 8-bit Tools" +.SH NAME +cxrefbas \- Code cross-reference for tokenized Atari 8-bit BASIC files +.SH SYNOPSIS +.sp +cxrefbas \fBinput\-file\fP +.SH DESCRIPTION +.sp +\fBcxrefbas\fP reads an Atari 8\-bit BASIC tokenized program. For each +line number in the program, it prints a list of lines that reference +it, via \fIGOTO\fP, \fIGOSUB\fP, \fIRESTORE\fP, \fITRAP\fP, \fION/GOTO\fP, or \fION/GOSUB\fP\&. +.sp +If a line doesn\(aqt exist, but is referenced (e.g. \fIGOTO 100\fP, but there +is no line 100), it\(aqs printed in the table, prefixed with \fI!\fP\&. +.sp +Any command that uses a computed value for a line number will print a +warning on standard error, e.g. \fIGOTO A\fP or \fIGOSUB 100*A\fP\&. Even \fIGOTO +100+0\fP is a computed value, since BASIC doesn\(aqt do constant folding. +.SH OPTIONS +.SS General Options +.INDENT 0.0 +.TP +.B \fB\-\-help\fP +Print usage message and exit. +.TP +.B \fB\-\-version\fP +Print version number and exit. +.TP +.B \fB\-v\fP +Verbose operation. When displaying a number in verbose mode, it will +be prefixed with \fI$\fP if it\(aqs in hex, or no prefix for decimal. +.UNINDENT +.SH EXIT STATUS +.sp +0 for success, 1 for failure. +.SH COPYRIGHT +.sp +WTFPL. See \fI\%http://www.wtfpl.net/txt/copying/\fP for details. +.SH AUTHOR +.INDENT 0.0 +.IP B. 3 +Watson <\fI\%urchlay@slackware.uk\fP>; Urchlay on irc.libera.chat \fI##atari\fP\&. +.UNINDENT +.SH SEE ALSO +.sp +\fBa8eol\fP(1), +\fBa8utf8\fP(1), +\fBatr2xfd\fP(1), +\fBatrsize\fP(1), +\fBaxe\fP(1), +\fBblob2c\fP(1), +\fBblob2xex\fP(1), +\fBcart2xex\fP(1), +\fBdasm2atasm\fP(1), +\fBdumpbas\fP(1), +\fBf2toxex\fP(1), +\fBfenders\fP(1), +\fBprotbas\fP(1), +\fBrenumbas\fP(1), +\fBrom2cart\fP(1), +\fBunmac65\fP(1), +\fBunprotbas\fP(1), +\fBvxrefbas\fP(1), +\fBxexamine\fP(1), +\fBxexcat\fP(1), +\fBxexsplit\fP(1), +\fBxfd2atr\fP(1), +\fBxex\fP(5), +\fBatascii\fP(7). +.sp +Any good Atari 8\-bit book: \fIDe Re Atari\fP, \fIThe Atari BASIC Reference +Manual\fP, the \fIOS Users\(aq Guide\fP, \fIMapping the Atari\fP, etc. +.\" Generated by docutils manpage writer. +. diff --git a/cxrefbas.c b/cxrefbas.c new file mode 100644 index 0000000..45369d4 --- /dev/null +++ b/cxrefbas.c @@ -0,0 +1,198 @@ +#include +#include +#include +#include +#include +#include + +#include "bas.h" +#include "bcdfp.h" + +unsigned short *linerefs[32769]; +int refcounts[32769]; +int lines_exist[32769]; +int last_cmd; +int last_cmd_pos; + +void add_lineref(unsigned short from, unsigned short to) { + unsigned short *p; + int c; + + if(to > 32767) return; + + p = linerefs[to]; + c = refcounts[to]; + + if(c) { + p = realloc(p, sizeof(unsigned short) * (c + 1)); + } else { + p = malloc(sizeof(unsigned short)); + } + + if(!p) die("Out of memory."); + + linerefs[to] = p; + p[c] = from; + c++; + refcounts[to] = c; +} + +/* makes sure a numeric constant isn't start of an expression. */ +int is_standalone_num(unsigned short pos) { + if(program[pos] != OP_NUMCONST) return 0; + switch(program[pos + 7]) { + case OP_EOS: + case OP_EOL: + case OP_COMMA: + return 1; + default: + return 0; + } +} + +CALLBACK(start_stmt) { + lines_exist[lineno] = 1; +} + +CALLBACK(got_cmd) { + last_cmd = tok; + last_cmd_pos = pos; +} + +void computed_msg(unsigned char tok, unsigned short lineno) { + static int last_lineno = -1; + char *cmd; + + /* avoid duplicate warnings */ + if(lineno == last_lineno) return; + last_lineno = lineno; + + switch(tok) { + case CMD_GOTO: + cmd = "GOTO"; break; + case CMD_GO_TO: + cmd = "GO TO"; break; + case CMD_GOSUB: + cmd = "GOSUB"; break; + case CMD_RESTORE: + cmd = "RESTORE"; break; + case CMD_TRAP: + cmd = "TRAP"; break; + case CMD_ON: + cmd = "ON"; break; + default: /* should never happen! */ + cmd = "???"; break; + } + + fprintf(stderr, "Computed %s at line %d\n", cmd, lineno); +} + +CALLBACK(got_var) { + switch(last_cmd) { + case CMD_GOTO: + case CMD_GO_TO: + case CMD_GOSUB: + case CMD_RESTORE: + case CMD_TRAP: + computed_msg(last_cmd, lineno); + break; + default: + break; + } +} + +CALLBACK(got_exp) { + int standalone; + + if(tok != OP_NUMCONST) return; + standalone = is_standalone_num(pos); + + switch(last_cmd) { + case CMD_GOTO: + case CMD_GO_TO: + case CMD_GOSUB: + case CMD_RESTORE: + case CMD_TRAP: + if((pos == last_cmd_pos + 1) && standalone) { + add_lineref(lineno, fp2int(program + pos + 1)); + } else { + computed_msg(last_cmd, lineno); + } + break; + case CMD_ON: { + unsigned char last_tok = program[pos - 1]; + switch(last_tok) { + case OP_GOTO: + case OP_GOSUB: + case OP_COMMA: + if(standalone) + add_lineref(lineno, fp2int(program + pos + 1)); + else + computed_msg(last_cmd, lineno); + break; + default: + break; + } + } + break; + default: + break; + } +} + +void build_ref_table(void) { + on_start_stmt = start_stmt; + on_cmd_token = got_cmd; + on_exp_token = got_exp; + on_var_token = got_var; + walk_all_code(); +} + +void print_ref_table(void) { + int i, j; + for(i = 0; i < 32768; i++) { + if(refcounts[i]) { + if(!lines_exist[i]) putchar('!'); + printf("%d: ", i); + for(j = 0; j < refcounts[i]; j++) { + printf("%d ", linerefs[i][j]); + } + putchar('\n'); + } + } +} + +void print_help(void) { + fprintf(stderr, "Usage: %s [-v] program.bas\n", self); + exit(0); +} + +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 (use - for stdin)."); + else + open_input(argv[optind]); +} + +int main(int argc, char **argv) { + set_self(*argv); + parse_general_args(argc, argv, print_help); + parse_args(argc, argv); + + readfile(); + parse_header(); + + build_ref_table(); + print_ref_table(); + + return 0; +} diff --git a/cxrefbas.rst b/cxrefbas.rst new file mode 100644 index 0000000..fbfc74a --- /dev/null +++ b/cxrefbas.rst @@ -0,0 +1,50 @@ +======== +cxrefbas +======== + +---------------------------------------------------------- +Code cross-reference for tokenized Atari 8-bit BASIC files +---------------------------------------------------------- + +.. include:: manhdr.rst + +SYNOPSIS +======== + +cxrefbas **input-file** + +DESCRIPTION +=========== + +**cxrefbas** reads an Atari 8-bit BASIC tokenized program. For each +line number in the program, it prints a list of lines that reference +it, via *GOTO*, *GOSUB*, *RESTORE*, *TRAP*, *ON/GOTO*, or *ON/GOSUB*. + +If a line doesn't exist, but is referenced (e.g. *GOTO 100*, but there +is no line 100), it's printed in the table, prefixed with *!*. + +Any command that uses a computed value for a line number will print a +warning on standard error, e.g. *GOTO A* or *GOSUB 100\*A*. Even *GOTO +100+0* is a computed value, since BASIC doesn't do constant folding. + +OPTIONS +======= + +General Options +--------------- +**--help** + Print usage message and exit. + +**--version** + Print version number and exit. + +**-v** + Verbose operation. When displaying a number in verbose mode, it will + be prefixed with *$* if it's in hex, or no prefix for decimal. + +EXIT STATUS +=========== + +0 for success, 1 for failure. + +.. include:: manftr.rst diff --git a/vxrefbas.c b/vxrefbas.c index 0cffc64..5ae6694 100644 --- a/vxrefbas.c +++ b/vxrefbas.c @@ -13,7 +13,7 @@ unsigned char last_cmd = 0; int refcounts[128]; void print_help(void) { - fprintf(stderr, "Usage: %s program.bas\n", self); + fprintf(stderr, "Usage: %s [-v] program.bas\n", self); exit(0); } -- cgit v1.2.3