aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorB. Watson <urchlay@slackware.uk>2024-06-16 17:33:22 -0400
committerB. Watson <urchlay@slackware.uk>2024-06-16 17:33:22 -0400
commite2250e5cb71c9a06bf75d59e9c0369efd65b1206 (patch)
treeae87da4b129ca29c5066e27d375f080d0cac9d72
parentdecd8312b033235cb64db2fcd3ffa4829bd21af7 (diff)
downloadbw-atari8-tools-e2250e5cb71c9a06bf75d59e9c0369efd65b1206.tar.gz
cxrefbas: added.
-rw-r--r--Makefile6
-rw-r--r--bas.h1
-rw-r--r--bcdfp.c56
-rw-r--r--bcdfp.h4
-rw-r--r--cxrefbas.1103
-rw-r--r--cxrefbas.c198
-rw-r--r--cxrefbas.rst50
-rw-r--r--vxrefbas.c2
8 files changed, 417 insertions, 3 deletions
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 <string.h>
+#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 <stdio.h>
+#include <unistd.h>
+#include <stdlib.h>
+#include <string.h>
+#include <ctype.h>
+#include <time.h>
+
+#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);
}