aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--.gitignore19
-rw-r--r--Makefile20
-rw-r--r--README.txt4
-rw-r--r--a8cat.11
-rw-r--r--a8eol.11
-rw-r--r--a8xd.11
-rw-r--r--atascii.71
-rw-r--r--atr2xfd.11
-rw-r--r--atrsize.11
-rw-r--r--axe.11
-rw-r--r--bas.c18
-rw-r--r--blob2c.11
-rw-r--r--blob2xex.11
-rw-r--r--cart2xex.11
-rw-r--r--cxrefbas.11
-rw-r--r--dasm2atasm.11
-rw-r--r--dumpbas.11
-rw-r--r--fenders.11
-rw-r--r--listbas.188
-rw-r--r--listbas.c209
-rw-r--r--listbas.rst77
-rw-r--r--protbas.11
-rw-r--r--renumbas.11
-rw-r--r--rom2cart.11
-rw-r--r--unmac65.11
-rw-r--r--unprotbas.11
-rw-r--r--vxrefbas.11
-rw-r--r--whichbas.c417
-rw-r--r--whichbas.rst84
-rw-r--r--xex.51
-rw-r--r--xex1to2.11
-rw-r--r--xexamine.11
-rw-r--r--xexcat.11
-rw-r--r--xexsplit.11
-rw-r--r--xfd2atr.11
35 files changed, 878 insertions, 84 deletions
diff --git a/.gitignore b/.gitignore
index 98ef547..8cb8268 100644
--- a/.gitignore
+++ b/.gitignore
@@ -1,11 +1,15 @@
*.bas
*.BAS
+*.LST
+*.XEX
*.car
*.cart
*.rom
*.swp
*.syms
+*.a8s
1
+1.*
2
3
*.atr
@@ -44,3 +48,18 @@ ksiders/atrextr
ksiders/makeatr
ksiders/sortatr
ksiders/unmakatr
+jindroush/acvt/acvt
+jindroush/acvt/switches.cpp
+jindroush/adir/adir
+jindroush/adir/switches.cpp
+jindroush/aext/aext
+jindroush/aext/switches.cpp
+jindroush/bas2boot/bas2boot
+jindroush/bas2boot/switches.cpp
+jindroush/chkbas/chkbas
+jindroush/chkbas/switches.cpp
+jindroush/chkexe/chkexe
+jindroush/chkexe/switches.cpp
+jindroush/chkrom/chkrom
+jindroush/chkrom/switches.cpp
+jindroush/lib/libjindroush.a
diff --git a/Makefile b/Makefile
index ac7c4b3..f96dd43 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 cxrefbas dumpbas fenders protbas renumbas rom2cart unmac65 unprotbas vxrefbas xex1to2 xexamine xexcat xexsplit xfd2atr listbas a8cat a8xd
+BINS=a8eol atr2xfd atrsize axe blob2c blob2xex cart2xex cxrefbas dumpbas fenders protbas renumbas rom2cart unmac65 unprotbas vxrefbas xex1to2 xexamine xexcat xexsplit xfd2atr listbas a8cat a8xd whichbas
SCRIPTS=dasm2atasm
-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 blob2xex.1 xexamine.1 xex1to2.1 unprotbas.1 protbas.1 renumbas.1 dumpbas.1 vxrefbas.1 cxrefbas.1 listbas.1 a8cat.1 a8xd.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 blob2xex.1 xexamine.1 xex1to2.1 unprotbas.1 protbas.1 renumbas.1 dumpbas.1 vxrefbas.1 cxrefbas.1 listbas.1 a8cat.1 a8xd.1 whichbas.1
MAN5S=xex.5
MAN7S=atascii.7
DOCS=README.txt equates.inc *.dasm LICENSE ksiders/atr.txt
@@ -57,12 +57,14 @@ renumbas: bas.o bcdfp.o linetab.o
dumpbas: bas.o
+whichbas: bas.o
+
vxrefbas: bas.o
cxrefbas: bas.o bcdfp.o linetab.o
-listbas: listbas.c bas.o bcdfp.o tokens.o
- $(CC) $(CFLAGS) -o listbas listbas.c bas.o bcdfp.o tokens.o -lm
+listbas: listbas.c bas.o bcdfp.o tokens.o atables.o
+ $(CC) $(CFLAGS) -o listbas listbas.c bas.o bcdfp.o tokens.o atables.o -lm
bas.o: bas.c bas.h
@@ -227,3 +229,13 @@ install-subdirs: subdirs
for dir in $(SUBDIRS); do \
make -C $$dir DESTDIR=$(DESTDIR) PREFIX=$(PREFIX) BINDIR=$(BINDIR) MAN1DIR=$(MAN1DIR) GZIP_MAN=$(GZIP_MAN) DOCDIR=$(DOCDIR) install; \
done
+
+user-symlinks: all
+ SRC=$$( pwd ) ; \
+ mkdir -p ~/bin ; \
+ cd ~/bin ; \
+ for i in $(BINS) \
+ ksiders/atrdir ksiders/atrextr ksiders/makeatr ksiders/sortatr ksiders/unmakatr \
+ jindroush/chkrom/chkrom jindroush/chkbas/chkbas jindroush/bas2boot/bas2boot \
+ jindroush/chkexe/chkexe jindroush/adir/adir jindroush/acvt/acvt jindroush/aext/aext \
+ ; do rm -f $$i; ln -s $$SRC/$$i . ; done
diff --git a/README.txt b/README.txt
index 76ff5cb..99d40b3 100644
--- a/README.txt
+++ b/README.txt
@@ -63,8 +63,8 @@ xexsplit - Split a multi-segment Atari 8-bit executable (XEX) into
xfd2atr - Convert an Atari 8-bit XFD (raw) disk image to an ATR image.
-All are written in C, except a8utf8 and dasm2atasm which are written
-in Perl. All utilities have man pages.
+All are written in C, except dasm2atasm which is written in Perl. All
+utilities have man pages.
Also included is "equates.inc", a 6502 assembly header file that defines
the Atari 8-bit system equates. It's meant to be used with either the
diff --git a/a8cat.1 b/a8cat.1
index 628cc38..c9d5d2b 100644
--- a/a8cat.1
+++ b/a8cat.1
@@ -158,6 +158,7 @@ Watson <\fI\%urchlay@slackware.uk\fP>; Urchlay on irc.libera.chat \fI##atari\fP\
\fBunmac65\fP(1),
\fBunprotbas\fP(1),
\fBvxrefbas\fP(1),
+\fBxex1to2\fP(1),
\fBxexamine\fP(1),
\fBxexcat\fP(1),
\fBxexsplit\fP(1),
diff --git a/a8eol.1 b/a8eol.1
index 6f9625a..31648ce 100644
--- a/a8eol.1
+++ b/a8eol.1
@@ -482,6 +482,7 @@ Watson <\fI\%urchlay@slackware.uk\fP>; Urchlay on irc.libera.chat \fI##atari\fP\
\fBunmac65\fP(1),
\fBunprotbas\fP(1),
\fBvxrefbas\fP(1),
+\fBxex1to2\fP(1),
\fBxexamine\fP(1),
\fBxexcat\fP(1),
\fBxexsplit\fP(1),
diff --git a/a8xd.1 b/a8xd.1
index 01c60bd..1bfde97 100644
--- a/a8xd.1
+++ b/a8xd.1
@@ -253,6 +253,7 @@ Watson <\fI\%urchlay@slackware.uk\fP>; Urchlay on irc.libera.chat \fI##atari\fP\
\fBunmac65\fP(1),
\fBunprotbas\fP(1),
\fBvxrefbas\fP(1),
+\fBxex1to2\fP(1),
\fBxexamine\fP(1),
\fBxexcat\fP(1),
\fBxexsplit\fP(1),
diff --git a/atascii.7 b/atascii.7
index 1d75746..ac08ec6 100644
--- a/atascii.7
+++ b/atascii.7
@@ -2171,6 +2171,7 @@ Watson <\fI\%urchlay@slackware.uk\fP>; Urchlay on irc.libera.chat \fI##atari\fP\
\fBunmac65\fP(1),
\fBunprotbas\fP(1),
\fBvxrefbas\fP(1),
+\fBxex1to2\fP(1),
\fBxexamine\fP(1),
\fBxexcat\fP(1),
\fBxexsplit\fP(1),
diff --git a/atr2xfd.1 b/atr2xfd.1
index 26f5b91..f46f6dd 100644
--- a/atr2xfd.1
+++ b/atr2xfd.1
@@ -202,6 +202,7 @@ Watson <\fI\%urchlay@slackware.uk\fP>; Urchlay on irc.libera.chat \fI##atari\fP\
\fBunmac65\fP(1),
\fBunprotbas\fP(1),
\fBvxrefbas\fP(1),
+\fBxex1to2\fP(1),
\fBxexamine\fP(1),
\fBxexcat\fP(1),
\fBxexsplit\fP(1),
diff --git a/atrsize.1 b/atrsize.1
index 847ba50..08504ff 100644
--- a/atrsize.1
+++ b/atrsize.1
@@ -216,6 +216,7 @@ Watson <\fI\%urchlay@slackware.uk\fP>; Urchlay on irc.libera.chat \fI##atari\fP\
\fBunmac65\fP(1),
\fBunprotbas\fP(1),
\fBvxrefbas\fP(1),
+\fBxex1to2\fP(1),
\fBxexamine\fP(1),
\fBxexcat\fP(1),
\fBxexsplit\fP(1),
diff --git a/axe.1 b/axe.1
index 72a3dc8..f2eaef0 100644
--- a/axe.1
+++ b/axe.1
@@ -157,6 +157,7 @@ Watson <\fI\%urchlay@slackware.uk\fP>; Urchlay on irc.libera.chat \fI##atari\fP\
\fBunmac65\fP(1),
\fBunprotbas\fP(1),
\fBvxrefbas\fP(1),
+\fBxex1to2\fP(1),
\fBxexamine\fP(1),
\fBxexcat\fP(1),
\fBxexsplit\fP(1),
diff --git a/bas.c b/bas.c
index efb985e..13385f1 100644
--- a/bas.c
+++ b/bas.c
@@ -171,15 +171,19 @@ int vntable_ok(void) {
/* first pass: bad = 1 if all the bytes in the table have the same
value, no matter what it is. */
vp = vnstart + 1;
- bad = 1;
- while(vp < vvstart - 1) {
- if(program[vp] != program[vnstart]) {
- bad = 0;
- break;
+
+ /* don't do this check if the table is only one byte long! */
+ if(vp < vvstart - 1) {
+ bad = 1;
+ while(vp < vvstart - 1) {
+ if(program[vp] != program[vnstart]) {
+ bad = 0;
+ break;
+ }
+ vp++;
}
- vp++;
+ if(bad) return 0;
}
- if(bad) return 0;
/* 2nd pass: bad = 1 if there's any invalid character in the table. */
vp = vnstart;
diff --git a/blob2c.1 b/blob2c.1
index ef17fe1..56c8977 100644
--- a/blob2c.1
+++ b/blob2c.1
@@ -137,6 +137,7 @@ Watson <\fI\%urchlay@slackware.uk\fP>; Urchlay on irc.libera.chat \fI##atari\fP\
\fBunmac65\fP(1),
\fBunprotbas\fP(1),
\fBvxrefbas\fP(1),
+\fBxex1to2\fP(1),
\fBxexamine\fP(1),
\fBxexcat\fP(1),
\fBxexsplit\fP(1),
diff --git a/blob2xex.1 b/blob2xex.1
index ebfdb91..6f06b32 100644
--- a/blob2xex.1
+++ b/blob2xex.1
@@ -228,6 +228,7 @@ Watson <\fI\%urchlay@slackware.uk\fP>; Urchlay on irc.libera.chat \fI##atari\fP\
\fBunmac65\fP(1),
\fBunprotbas\fP(1),
\fBvxrefbas\fP(1),
+\fBxex1to2\fP(1),
\fBxexamine\fP(1),
\fBxexcat\fP(1),
\fBxexsplit\fP(1),
diff --git a/cart2xex.1 b/cart2xex.1
index e226f28..349e637 100644
--- a/cart2xex.1
+++ b/cart2xex.1
@@ -246,6 +246,7 @@ Watson <\fI\%urchlay@slackware.uk\fP>; Urchlay on irc.libera.chat \fI##atari\fP\
\fBunmac65\fP(1),
\fBunprotbas\fP(1),
\fBvxrefbas\fP(1),
+\fBxex1to2\fP(1),
\fBxexamine\fP(1),
\fBxexcat\fP(1),
\fBxexsplit\fP(1),
diff --git a/cxrefbas.1 b/cxrefbas.1
index fc227db..893d47b 100644
--- a/cxrefbas.1
+++ b/cxrefbas.1
@@ -175,6 +175,7 @@ Watson <\fI\%urchlay@slackware.uk\fP>; Urchlay on irc.libera.chat \fI##atari\fP\
\fBunmac65\fP(1),
\fBunprotbas\fP(1),
\fBvxrefbas\fP(1),
+\fBxex1to2\fP(1),
\fBxexamine\fP(1),
\fBxexcat\fP(1),
\fBxexsplit\fP(1),
diff --git a/dasm2atasm.1 b/dasm2atasm.1
index b853071..18f0100 100644
--- a/dasm2atasm.1
+++ b/dasm2atasm.1
@@ -244,6 +244,7 @@ Watson <\fI\%urchlay@slackware.uk\fP>; Urchlay on irc.libera.chat \fI##atari\fP\
\fBunmac65\fP(1),
\fBunprotbas\fP(1),
\fBvxrefbas\fP(1),
+\fBxex1to2\fP(1),
\fBxexamine\fP(1),
\fBxexcat\fP(1),
\fBxexsplit\fP(1),
diff --git a/dumpbas.1 b/dumpbas.1
index 3703ee9..7eff370 100644
--- a/dumpbas.1
+++ b/dumpbas.1
@@ -225,6 +225,7 @@ Watson <\fI\%urchlay@slackware.uk\fP>; Urchlay on irc.libera.chat \fI##atari\fP\
\fBunmac65\fP(1),
\fBunprotbas\fP(1),
\fBvxrefbas\fP(1),
+\fBxex1to2\fP(1),
\fBxexamine\fP(1),
\fBxexcat\fP(1),
\fBxexsplit\fP(1),
diff --git a/fenders.1 b/fenders.1
index 6a3f9db..9aa9651 100644
--- a/fenders.1
+++ b/fenders.1
@@ -291,6 +291,7 @@ Watson <\fI\%urchlay@slackware.uk\fP>; Urchlay on irc.libera.chat \fI##atari\fP\
\fBunmac65\fP(1),
\fBunprotbas\fP(1),
\fBvxrefbas\fP(1),
+\fBxex1to2\fP(1),
\fBxexamine\fP(1),
\fBxexcat\fP(1),
\fBxexsplit\fP(1),
diff --git a/listbas.1 b/listbas.1
index 0ff5be2..6170b6a 100644
--- a/listbas.1
+++ b/listbas.1
@@ -27,7 +27,7 @@ level margin: \\n[rst2man-indent\\n[rst2man-indent-level]]
.\" new: \\n[rst2man-indent\\n[rst2man-indent-level]]
.in \\n[rst2man-indent\\n[rst2man-indent-level]]u
..
-.TH "LISTBAS" 1 "2024-07-02" "0.2.1" "Urchlay's Atari 8-bit Tools"
+.TH "LISTBAS" 1 "2024-07-05" "0.2.1" "Urchlay's Atari 8-bit Tools"
.SH NAME
listbas \- List the source of a tokenized Atari 8-bit BASIC program
.SH SYNOPSIS
@@ -39,26 +39,29 @@ listbas [\fB\-v\fP] [\fB\-i\fP] [\fB\-a\fP | \fB\-m\fP ] \fBinput\-file\fP
tokenized (SAVEd) BASIC program and prints the code in human\-readable
format.
.sp
-By default, output is piped through \fBa8cat\fP(1), to covert ATASCII
-characters into Unicode equivalents. Raw ATASCII and "magazine listing"
-mode are also available.
-.sp
-\fBa8cat\fP is run as an external processe. The executables is searched
-for first in the current directory, and then in \fIPATH\fP\&.
+By default, output is Unicode in UTF\-8 encoding, with ANSI/VT220
+escape sequences for inverse video and color syntax highlighting.
.SH OPTIONS
.SS List options
.INDENT 0.0
.TP
-.B \fB\-i\fP
-Include the immediate mode command (line 32768) in the output.
-.TP
.B \fB\-a\fP
Output raw ATASCII; no translation to the host character set. Must be
used with redirection; \fBlistbas\fP will not write ATASCII to the terminal.
.TP
+.B \fB\-b\fP
+Use bold, for color output. This may make it easier to read on
+some terminals. Or, it may hurt your eyes...
+.TP
+.B \fB\-i\fP
+Include the immediate mode command (line 32768) in the output.
+.TP
.B \fB\-m\fP
-Output "magazine listing". Pipes output through \fBa8cat \-m\fP; see the man
-page for \fBa8cat\fP for details.
+Output "magazine listing". See the \fB\-m\fP option for \fBa8cat\fP for details.
+.TP
+.B \fB\-n\fP
+No color. Has no effect if \fB\-a\fP or \fB\-m\fP are in effect, since these
+modes don\(aqt support color anyway.
.UNINDENT
.SS General Options
.INDENT 0.0
@@ -73,6 +76,42 @@ Print version number and exit.
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 COLORS
+.sp
+Color output only works on terminal emulators (or real terminals)
+that support ANSI/VT220 style escape codes. This includes all modern
+terminal emulators, and most not\-so\-modern ones in the UNIX world.
+.sp
+The color scheme is:
+.INDENT 0.0
+.TP
+.B \fByellow\fP
+Commands. Also "command operators" such as the \fBGOTO\fP in \fBON/GOTO\fP and
+the \fBSTEP\fP in a \fBFOR\fP command. These are really operators as far as
+BASIC is concerned, but it makes more sense to colorize them as commands.
+.TP
+.B \fBgreen\fP
+Operators (except functions and "command operators").
+.TP
+.B \fBpurple\fP
+Functions.
+.TP
+.B \fBred\fP
+Numbers (except line numbers at the start of a line) and string
+constants.
+.TP
+.B \fBcyan\fP
+Line numbers at the start of a line, comments (\fBREM\fP text) and \fBDATA\fP elements.
+.UNINDENT
+.sp
+Variable names and commas between \fBDATA\fP elements are not highlighted,
+so they\(aqll appear in the default foreground color (usually white if the
+terminal has a black background, or black if the background is white).
+.sp
+Note that nothing is highlighted in blue. This is because it\(aqs
+difficult to read on many terminals. Also, black and white are not
+used because presumably, one or the other is the background color of
+the terminal.
.SH NOTES
.sp
\fBlistbas\fP will refuse to operate on a LIST\-protected program with
@@ -83,6 +122,11 @@ protection.
\fBlistbas\fP is similar to Jindroush\(aqs \fBchkbas\fP(1). The main differences are:
.INDENT 0.0
.IP \(bu 2
+\fBlistbas\fP prints ATASCII graphics as Unicode equivalents, so the listing
+looks very similar to how it would appear on the Atari.
+.IP \(bu 2
+\fBlistbas\fP does color syntax highlighting.
+.IP \(bu 2
\fBlistbas\fP only supports Atari BASIC, not Turbo BASIC or BASIC XL/XE.
.IP \(bu 2
\fBlistbas\fP doesn\(aqt show information about the variables. Use \fBvxrefbas\fP(1)
@@ -101,9 +145,26 @@ specifically asked to do so.
\fBlistbas\fP tells you if the program is protected, and refuses to operate
on variable\-protected programs.
.UNINDENT
+.sp
+I thought about adding an HTML output option, but there\(aqs no need: if you want
+a colorful listing of an Atari BASIC program, install \fBaha\fP(1) from
+\fI\%https://github.com/theZiz/aha\fP (or your distro\(aqs package repo) and run
+something like:
+.INDENT 0.0
+.INDENT 3.5
+.sp
+.nf
+.ft C
+listbas PROGRAM.BAS | aha > program.html
+.ft P
+.fi
+.UNINDENT
+.UNINDENT
.SH EXIT STATUS
.sp
-0 for success, 1 for failure.
+0 for success, 1 if there was an error reading the input (e.g. file
+not found), or 2 if the input file has invalid tokens (if this
+happens, you will also see a warning about it on stderr).
.SH COPYRIGHT
.sp
WTFPL. See \fI\%http://www.wtfpl.net/txt/copying/\fP for details.
@@ -135,6 +196,7 @@ Watson <\fI\%urchlay@slackware.uk\fP>; Urchlay on irc.libera.chat \fI##atari\fP\
\fBunmac65\fP(1),
\fBunprotbas\fP(1),
\fBvxrefbas\fP(1),
+\fBxex1to2\fP(1),
\fBxexamine\fP(1),
\fBxexcat\fP(1),
\fBxexsplit\fP(1),
diff --git a/listbas.c b/listbas.c
index 9577cec..000cc95 100644
--- a/listbas.c
+++ b/listbas.c
@@ -11,28 +11,42 @@
#include "bas.h"
#include "bcdfp.h"
#include "tokens.h"
+#include "atables.h"
-int immediate = 0, a8cat = 1, magazine = 0;
+#define C_RED 1
+#define C_GREEN 2
+#define C_YELLOW 3
+#define C_PURPLE 5
+#define C_CYAN 6
+
+int immediate = 0, utf8 = 1, magazine = 0, inv = 0,
+ color = 1, bold = 0, badtok = 0;
+
+const char **table = ata2utf;
FILE *outfh;
void print_help(void) {
- printf("Usage: %s [-v] [-i] [-a|-u] <inputfile>\n", self);
- printf(" -v: verbose.\n");
- printf(" -i: show immediate mode command (line 32768).\n");
+ printf("Usage: %s [-a] [-b] [-i] [-m] [-n] [-v] <inputfile>\n", self);
printf(" -a: output raw ATASCII.\n");
+ printf(" -b: use bold for color output.\n");
+ printf(" -i: show immediate mode command (line 32768).\n");
printf(" -m: magazine style listing (see a8cat(1)).\n");
+ printf(" -n: disable color syntax highlighting.\n");
+ printf(" -v: verbose.\n");
}
void parse_args(int argc, char **argv) {
int opt;
- while( (opt = getopt(argc, argv, "viamh")) != -1) {
+ while( (opt = getopt(argc, argv, "viamnbh")) != -1) {
switch(opt) {
case 'v': verbose = 1; break;
case 'i': immediate = 1; break;
- case 'a': a8cat = magazine = 0; break;
- case 'm': a8cat = magazine = 1; break;
+ case 'a': utf8 = magazine = 0; color = 0; break;
+ case 'm': utf8 = 0 ; magazine = 1; color = 0; table = ata2mag; break;
+ case 'n': color = 0; break;
+ case 'b': bold = 1; break;
case 'h': print_help(); exit(0);
default:
print_help();
@@ -47,38 +61,13 @@ void parse_args(int argc, char **argv) {
}
void setup_outfh(void) {
- const char *cmd;
-
- /* search current dir before PATH. no easy way to detect errors here,
- have to wait until we call pclose(). */
- if(a8cat) {
- if(magazine)
- cmd = "./a8cat -m 2>/dev/null || a8cat -m 2>/dev/null || exit 1";
- else
- cmd = "./a8cat 2>/dev/null || a8cat 2>/dev/null || exit 1";
- } else {
+ if(! (utf8 || magazine) ) {
if(isatty(fileno(stdout))) {
die("Refusing to write ATASCII data to the terminal.");
}
- outfh = stdout;
- return;
- }
-
- outfh = popen(cmd, "w");
- if(!outfh) {
- /* fork() or pipe() failed. does NOT detect if the command
- wasn't found. */
- perror(self);
- exit(1);
- }
-}
-
-void close_outfh(void) {
- if(a8cat) {
- if(pclose(outfh)) {
- die("output filter failed; a8cat not in current dir or $PATH.");
- }
}
+ outfh = stdout;
+ return;
}
void outchr(char c) {
@@ -110,18 +99,100 @@ double bcd2double(const unsigned char *num) {
return result;
}
+void color_on(unsigned char c) {
+ printf("\x1b[%d;3%dm", bold, c);
+}
+
+void color_off(void) {
+ fputs("\x1b[0m", outfh);
+}
+
void print_number(unsigned int pos) {
+ if(color) color_on(C_RED);
fprintf(outfh, "%G", bcd2double(program + pos));
+ if(color) color_off();
+}
+
+/* only called in magazine mode. cursor control characters
+ like a bell in the middle of a non-inverse string
+ should not cause it to print {inv}{bell}{norm}. The {bell}
+ is "inherently" inverse (since its high bit is set) but doesn't
+ need to be printed that way. */
+int affects_inv(unsigned char c) {
+ switch(c) {
+ case 0x1b:
+ case 0x1c:
+ case 0x1d:
+ case 0x1e:
+ case 0x1f:
+ case 0x9b:
+ case 0x9c:
+ case 0x9d:
+ case 0x9e:
+ case 0x9f:
+ case 0x7d:
+ case 0x7e:
+ case 0x7f:
+ case 0xfd:
+ case 0xfe:
+ case 0xff:
+ return 0;
+ default:
+ return 1;
+ }
+}
+
+void start_inv(unsigned char c) {
+ if(utf8) {
+ fputs("\x1b[7m", outfh);
+ inv = 1;
+ } else if(magazine) {
+ if(affects_inv(c)) {
+ fputs("{inv}", outfh);
+ inv = 1;
+ }
+ }
+}
+
+void end_inv(unsigned char c) {
+ if(utf8) {
+ fputs("\x1b[0m", outfh);
+ inv = 0;
+ } else if(magazine) {
+ if(affects_inv(c)) {
+ fputs("{norm}", outfh);
+ inv = 0;
+ }
+ }
+}
+
+void print_ata_chr(unsigned char c) {
+ if(c & 0x80) {
+ if(!inv) {
+ start_inv(c);
+ }
+ } else {
+ if(inv) {
+ end_inv(c);
+ }
+ }
+ fputs(table[c & 0x7f], outfh);
}
void print_string(unsigned int pos, unsigned int len) {
+ inv = 0;
+ if(color) color_on(C_RED);
outchr('"');
- while(len--) outchr(program[pos++]);
+ while(len--) print_ata_chr(program[pos++]);
+ if(inv) end_inv(0);
outchr('"');
+ if(color) color_off();
}
CALLBACK(print_lineno) {
+ if(color) color_on(C_CYAN);
fprintf(outfh, "%d ", lineno);
+ if(color) color_off();
}
CALLBACK(print_cmd) {
@@ -129,10 +200,14 @@ CALLBACK(print_cmd) {
if(tok == CMD_ILET) return;
- if(tok > last_command || (!(name = commands[tok])))
+ if(color) color_on(C_YELLOW);
+ if(tok > last_command || (!(name = commands[tok]))) {
fprintf(outfh, "(bad cmd token $%02x) ", tok);
- else
+ badtok = 1;
+ } else {
fprintf(outfh, "%s ", name);
+ }
+ if(color) color_off();
}
CALLBACK(print_op) {
@@ -150,36 +225,74 @@ CALLBACK(print_op) {
default: break;
}
- if(tok > last_operator || (!(name = operators[tok])))
+ if(color) {
+ if(tok > 0x3c)
+ color_on(C_PURPLE);
+ else if(tok == OP_UMINUS)
+ color_on(C_RED); /* show leading - in same color as the number */
+ else if((tok >= 0x17 && tok <= 0x1b) || (tok >= 0x28 && tok <= 0x2a))
+ color_on(C_YELLOW);
+ else
+ color_on(C_GREEN);
+ }
+ if(tok > last_operator || (!(name = operators[tok]))) {
fprintf(outfh, "(bad op token $%02x)", tok);
- else
+ badtok = 1;
+ } else {
fprintf(outfh, "%s", name);
+ }
+ if(color) color_off();
}
CALLBACK(print_varname) {
int i, count;
- unsigned char c;
+ unsigned char c, c_on = 0;;
tok &= 0x7f;
for(i = vnstart, count = 0; count < tok; i++) {
if(program[i] & 0x80) count++;
}
do {
- outchr( (c = program[i++]) & 0x7f);
+ c = program[i++];
+ if(color && c == ('(' | 0x80)) {
+ color_on(C_GREEN);
+ c_on = 1;
+ }
+ outchr(c & 0x7f);
} while (c < 0x80);
+ if(c_on) color_off();
}
CALLBACK(print_text) {
- while(program[pos] != 0x9b) outchr(program[pos++]);
+ unsigned char c, is_data = program[pos - 1] == CMD_DATA, comma = 0;
+
+ inv = 0;
+ if(color) color_on(C_CYAN);
+ while(program[pos] != 0x9b) {
+ c = program[pos++];
+ if(color && is_data && c == ',') {
+ color_off();
+ comma = 1;
+ } else {
+ comma = 0;
+ }
+ print_ata_chr(c);
+ if(comma)
+ color_on(C_CYAN);
+ }
+ if(inv) end_inv(0);
+ if(color) color_off();
}
CALLBACK(print_newline) {
- outchr(0x9b);
+ if(utf8 || magazine)
+ outchr('\n');
+ else
+ outchr(0x9b);
}
CALLBACK(code_prot) {
fprintf(stderr, "%s: Program is code-protected, stopping at line %d.\n", self, lineno);
- close_outfh();
exit(0);
}
@@ -207,7 +320,11 @@ int main(int argc, char **argv) {
setup_outfh();
list();
- close_outfh();
+
+ if(badtok) {
+ fprintf(stderr, "%s: program has unknown tokens; maybe Turbo BASIC or BASIC XL/XE?\n", self);
+ return 2;
+ }
return 0;
}
diff --git a/listbas.rst b/listbas.rst
index d6d17b1..881da5f 100644
--- a/listbas.rst
+++ b/listbas.rst
@@ -20,12 +20,8 @@ DESCRIPTION
tokenized (SAVEd) BASIC program and prints the code in human-readable
format.
-By default, output is piped through **a8cat**\(1), to covert ATASCII
-characters into Unicode equivalents. Raw ATASCII and "magazine listing"
-mode are also available.
-
-**a8cat** is run as an external processe. The executables is searched
-for first in the current directory, and then in *PATH*.
+By default, output is Unicode in UTF-8 encoding, with ANSI/VT220
+escape sequences for inverse video and color syntax highlighting.
OPTIONS
=======
@@ -33,19 +29,62 @@ OPTIONS
List options
------------
-**-i**
- Include the immediate mode command (line 32768) in the output.
-
**-a**
Output raw ATASCII; no translation to the host character set. Must be
used with redirection; **listbas** will not write ATASCII to the terminal.
+**-b**
+ Use bold, for color output. This may make it easier to read on
+ some terminals. Or, it may hurt your eyes...
+
+**-i**
+ Include the immediate mode command (line 32768) in the output.
+
**-m**
- Output "magazine listing". Pipes output through **a8cat -m**; see the man
- page for **a8cat** for details.
+ Output "magazine listing". See the **-m** option for **a8cat** for details.
+
+**-n**
+ No color. Has no effect if **-a** or **-m** are in effect, since these
+ modes don't support color anyway.
.. include:: genopts.rst
+COLORS
+======
+
+Color output only works on terminal emulators (or real terminals)
+that support ANSI/VT220 style escape codes. This includes all modern
+terminal emulators, and most not-so-modern ones in the UNIX world.
+
+The color scheme is:
+
+**yellow**
+ Commands. Also "command operators" such as the **GOTO** in **ON/GOTO** and
+ the **STEP** in a **FOR** command. These are really operators as far as
+ BASIC is concerned, but it makes more sense to colorize them as commands.
+
+**green**
+ Operators (except functions and "command operators").
+
+**purple**
+ Functions.
+
+**red**
+ Numbers (except line numbers at the start of a line) and string
+ constants.
+
+**cyan**
+ Line numbers at the start of a line, comments (**REM** text) and **DATA** elements.
+
+Variable names and commas between **DATA** elements are not highlighted,
+so they'll appear in the default foreground color (usually white if the
+terminal has a black background, or black if the background is white).
+
+Note that nothing is highlighted in blue. This is because it's
+difficult to read on many terminals. Also, black and white are not
+used because presumably, one or the other is the background color of
+the terminal.
+
NOTES
=====
@@ -56,6 +95,11 @@ protection.
**listbas** is similar to Jindroush's **chkbas**\(1). The main differences are:
+- **listbas** prints ATASCII graphics as Unicode equivalents, so the listing
+ looks very similar to how it would appear on the Atari.
+
+- **listbas** does color syntax highlighting.
+
- **listbas** only supports Atari BASIC, not Turbo BASIC or BASIC XL/XE.
- **listbas** doesn't show information about the variables. Use **vxrefbas**\(1)
@@ -74,9 +118,18 @@ protection.
- **listbas** tells you if the program is protected, and refuses to operate
on variable-protected programs.
+I thought about adding an HTML output option, but there's no need: if you want
+a colorful listing of an Atari BASIC program, install **aha**\(1) from
+https://github.com/theZiz/aha (or your distro's package repo) and run
+something like::
+
+ listbas PROGRAM.BAS | aha > program.html
+
EXIT STATUS
===========
-0 for success, 1 for failure.
+0 for success, 1 if there was an error reading the input (e.g. file
+not found), or 2 if the input file has invalid tokens (if this
+happens, you will also see a warning about it on stderr).
.. include:: manftr.rst
diff --git a/protbas.1 b/protbas.1
index 6a0d022..6d60f3d 100644
--- a/protbas.1
+++ b/protbas.1
@@ -132,6 +132,7 @@ Watson <\fI\%urchlay@slackware.uk\fP>; Urchlay on irc.libera.chat \fI##atari\fP\
\fBunmac65\fP(1),
\fBunprotbas\fP(1),
\fBvxrefbas\fP(1),
+\fBxex1to2\fP(1),
\fBxexamine\fP(1),
\fBxexcat\fP(1),
\fBxexsplit\fP(1),
diff --git a/renumbas.1 b/renumbas.1
index 3a33040..9252335 100644
--- a/renumbas.1
+++ b/renumbas.1
@@ -194,6 +194,7 @@ Watson <\fI\%urchlay@slackware.uk\fP>; Urchlay on irc.libera.chat \fI##atari\fP\
\fBunmac65\fP(1),
\fBunprotbas\fP(1),
\fBvxrefbas\fP(1),
+\fBxex1to2\fP(1),
\fBxexamine\fP(1),
\fBxexcat\fP(1),
\fBxexsplit\fP(1),
diff --git a/rom2cart.1 b/rom2cart.1
index 0d9676a..bba640b 100644
--- a/rom2cart.1
+++ b/rom2cart.1
@@ -260,6 +260,7 @@ Watson <\fI\%urchlay@slackware.uk\fP>; Urchlay on irc.libera.chat \fI##atari\fP\
\fBunmac65\fP(1),
\fBunprotbas\fP(1),
\fBvxrefbas\fP(1),
+\fBxex1to2\fP(1),
\fBxexamine\fP(1),
\fBxexcat\fP(1),
\fBxexsplit\fP(1),
diff --git a/unmac65.1 b/unmac65.1
index b4cc01a..6a1598e 100644
--- a/unmac65.1
+++ b/unmac65.1
@@ -392,6 +392,7 @@ Watson <\fI\%urchlay@slackware.uk\fP>; Urchlay on irc.libera.chat \fI##atari\fP\
\fBunmac65\fP(1),
\fBunprotbas\fP(1),
\fBvxrefbas\fP(1),
+\fBxex1to2\fP(1),
\fBxexamine\fP(1),
\fBxexcat\fP(1),
\fBxexsplit\fP(1),
diff --git a/unprotbas.1 b/unprotbas.1
index e4372a0..c521116 100644
--- a/unprotbas.1
+++ b/unprotbas.1
@@ -359,6 +359,7 @@ Watson <\fI\%urchlay@slackware.uk\fP>; Urchlay on irc.libera.chat \fI##atari\fP\
\fBunmac65\fP(1),
\fBunprotbas\fP(1),
\fBvxrefbas\fP(1),
+\fBxex1to2\fP(1),
\fBxexamine\fP(1),
\fBxexcat\fP(1),
\fBxexsplit\fP(1),
diff --git a/vxrefbas.1 b/vxrefbas.1
index c6b90b8..5804aa5 100644
--- a/vxrefbas.1
+++ b/vxrefbas.1
@@ -135,6 +135,7 @@ Watson <\fI\%urchlay@slackware.uk\fP>; Urchlay on irc.libera.chat \fI##atari\fP\
\fBunmac65\fP(1),
\fBunprotbas\fP(1),
\fBvxrefbas\fP(1),
+\fBxex1to2\fP(1),
\fBxexamine\fP(1),
\fBxexcat\fP(1),
\fBxexsplit\fP(1),
diff --git a/whichbas.c b/whichbas.c
new file mode 100644
index 0000000..dabf414
--- /dev/null
+++ b/whichbas.c
@@ -0,0 +1,417 @@
+#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;
+
+int keep_going = 0; /* -k flag */
+
+void print_help(void) {
+ printf("Usage: %s [-v] [-k] <inputfile>\n", self);
+}
+
+void parse_args(int argc, char **argv) {
+ int opt;
+
+ while( (opt = getopt(argc, argv, "vk")) != -1) {
+ switch(opt) {
+ case 'v': verbose = 1; break;
+ case 'k': keep_going = 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 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);
+}
+
+void remove_type(int type) {
+ bas_type &= ((~type) & 0x0f);
+
+ if(keep_going) return;
+
+ /* without -k, stop if it gets narrowed down to one of these two. */
+ if(bas_type == BT_TURBO || bas_type == BT_BXE)
+ print_result();
+}
+
+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\n", 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, " bas_type 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\n", 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, " bas_type 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, " bas_type 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\n", 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 */
+}
diff --git a/whichbas.rst b/whichbas.rst
new file mode 100644
index 0000000..3eb8854
--- /dev/null
+++ b/whichbas.rst
@@ -0,0 +1,84 @@
+========
+whichbas
+========
+
+----------------------------------------------------------
+Determine BASIC variant of a tokenized Atari 8-bit program
+----------------------------------------------------------
+
+.. include:: manhdr.rst
+
+SYNOPSIS
+========
+whichbas [-v] *input-file*
+
+DESCRIPTION
+===========
+**whichbas** reads a tokenized Atari 8-bit BASIC, Turbo BASIC,
+BASIC XL, BASIC XE, or Atari Microsoft BASIC program and attempts to
+discover which BASIC is required to run it.
+
+OPTIONS
+=======
+
+Detection Options
+-----------------
+
+**-k**
+ Keep going. The default is to stop looking at the program if the
+ BASIC type gets narrowed down to either Turbo BASIC XL or BASIC XE.
+ This option also enables **-v** (verbose). It's really only useful
+ for testing, if you're hacking on **whichbas** itself.
+
+.. include:: genopts.rst
+
+NOTES
+=====
+Turbo BASIC, BASIC XL, and BASIC XE are all supersets of Atari BASIC.
+If you wrote a program using one of them, but didn't use any of the
+extra commands or functions, the result is still an Atari BASIC program.
+
+There are two types of BASIC XE programs: regular and *EXTEND*\ed. The
+extended type is detected 100% reliably, because the first byte of the
+file changes from **$00** to **$DD**. Non-extended programs are only
+identified as BASIC XE if they use any of the extra commands BASIC XE
+adds to those found in BASIC XL.
+
+Atari BASIC programs can be detected 100% reliably.
+
+Detection of Turbo vs. BXL/BXE isn't 100% reliable, and probably
+never will be. There's too much overlap between the sets of extra
+tokens added by each. Programs that don't use very many of the extra
+functions provided by Turbo/BXL/BXE may show up as "Not Atari BASIC;
+probably either Turbo or BXL/BXE".
+
+Atari Microsoft BASIC is detected by checking that the first two
+bytes of the file are not zero, and that the last 3 are zero. This
+may result in false positives (files that aren't BASIC programs at
+all might show up as Microsoft). Also, no distinction is made between
+Atari MS BASIC 1.0 and 2.0.
+
+Various non-BASIC files are detected (including Mac/65 source,
+ELF binaries, etc) as a convenience, but I wouldn't rely on
+**whichbas**\'s non-BASIC file type detection if I were you.
+
+LIMITATIONS
+===========
+Currently, **whichbas** doesn't look at the variable name or type
+tables. One problem caused by this: If a program uses only Atari BASIC
+tokens, but uses variable(s) with _ in the name, it will be identified
+as Atari BASIC... even though _ in variable names is illegal in Atari
+BASIC and pretty much guarantees the program is Turbo/BXL/BXE.
+
+Looking at the variable types could also improve detection, since
+Turbo and BXL/BXE support extended variable types.
+
+**whichbas** knows nothing about other BASICs such as Frost BASIC,
+BASIC/A+, Altirra BASIC...
+
+EXIT STATUS
+===========
+
+0 for success, 1 for failure.
+
+.. include:: manftr.rst
diff --git a/xex.5 b/xex.5
index 6350c79..b44a612 100644
--- a/xex.5
+++ b/xex.5
@@ -319,6 +319,7 @@ Watson <\fI\%urchlay@slackware.uk\fP>; Urchlay on irc.libera.chat \fI##atari\fP\
\fBunmac65\fP(1),
\fBunprotbas\fP(1),
\fBvxrefbas\fP(1),
+\fBxex1to2\fP(1),
\fBxexamine\fP(1),
\fBxexcat\fP(1),
\fBxexsplit\fP(1),
diff --git a/xex1to2.1 b/xex1to2.1
index 13de3f8..89d1698 100644
--- a/xex1to2.1
+++ b/xex1to2.1
@@ -93,6 +93,7 @@ Watson <\fI\%urchlay@slackware.uk\fP>; Urchlay on irc.libera.chat \fI##atari\fP\
\fBunmac65\fP(1),
\fBunprotbas\fP(1),
\fBvxrefbas\fP(1),
+\fBxex1to2\fP(1),
\fBxexamine\fP(1),
\fBxexcat\fP(1),
\fBxexsplit\fP(1),
diff --git a/xexamine.1 b/xexamine.1
index 9c87b78..31f8283 100644
--- a/xexamine.1
+++ b/xexamine.1
@@ -151,6 +151,7 @@ Watson <\fI\%urchlay@slackware.uk\fP>; Urchlay on irc.libera.chat \fI##atari\fP\
\fBunmac65\fP(1),
\fBunprotbas\fP(1),
\fBvxrefbas\fP(1),
+\fBxex1to2\fP(1),
\fBxexamine\fP(1),
\fBxexcat\fP(1),
\fBxexsplit\fP(1),
diff --git a/xexcat.1 b/xexcat.1
index dd37231..9c9a441 100644
--- a/xexcat.1
+++ b/xexcat.1
@@ -211,6 +211,7 @@ Watson <\fI\%urchlay@slackware.uk\fP>; Urchlay on irc.libera.chat \fI##atari\fP\
\fBunmac65\fP(1),
\fBunprotbas\fP(1),
\fBvxrefbas\fP(1),
+\fBxex1to2\fP(1),
\fBxexamine\fP(1),
\fBxexcat\fP(1),
\fBxexsplit\fP(1),
diff --git a/xexsplit.1 b/xexsplit.1
index 822ff06..e31ec48 100644
--- a/xexsplit.1
+++ b/xexsplit.1
@@ -204,6 +204,7 @@ Watson <\fI\%urchlay@slackware.uk\fP>; Urchlay on irc.libera.chat \fI##atari\fP\
\fBunmac65\fP(1),
\fBunprotbas\fP(1),
\fBvxrefbas\fP(1),
+\fBxex1to2\fP(1),
\fBxexamine\fP(1),
\fBxexcat\fP(1),
\fBxexsplit\fP(1),
diff --git a/xfd2atr.1 b/xfd2atr.1
index 009e2bb..9b23a6d 100644
--- a/xfd2atr.1
+++ b/xfd2atr.1
@@ -132,6 +132,7 @@ Watson <\fI\%urchlay@slackware.uk\fP>; Urchlay on irc.libera.chat \fI##atari\fP\
\fBunmac65\fP(1),
\fBunprotbas\fP(1),
\fBvxrefbas\fP(1),
+\fBxex1to2\fP(1),
\fBxexamine\fP(1),
\fBxexcat\fP(1),
\fBxexsplit\fP(1),