Debianization.
[bascat] / bascat.c
CommitLineData
0398bb0d 1/* -*-c-*-
2 *
01880456 3 * $Id$
0398bb0d 4 *
5 * Display BBC BASIC programs more or less anywhere
6 *
7 * (c) 1996, 1997 Matthew Wilcox and Mark Wooding
8 */
9
10/*----- Licensing notice --------------------------------------------------*
11 *
12 * This file is part of Bascat.
13 *
14 * Bascat is free software; you can redistribute it and/or modify it
15 * under the terms of the GNU Library General Public License as
16 * published by the Free Software Foundation; either version 2 of the
17 * License, or (at your option) any later version.
18 *
19 * Bascat is distributed in the hope that it will be useful, but
20 * WITHOUT ANY WARRANTY; without even the implied warranty of
21 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
22 * GNU Library General Public License for more details.
23 *
24 * You should have received a copy of the GNU General Public License
25 * along with Bascat; if not, write to the Free Software Foundation,
26 * Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
27 */
28
0398bb0d 29/*----- Header files ------------------------------------------------------*/
30
31/* --- ANSI library headers --- */
32
33#include <ctype.h>
34#include <errno.h>
35#include <signal.h>
36#include <stdio.h>
37#include <stdlib.h>
38#include <string.h>
39
40/* --- Operating system specific headers --- */
41
42#ifdef HAVE_LIBTERMCAP
43# include <termcap.h>
44#endif
45#include <unistd.h>
46
47/* --- Private headers --- */
48
49#include "mdwopt.h"
50
51/*----- Version information -----------------------------------------------*/
52
53#ifndef NDEBUG
54# define D(x) x
55#else
56# define D(x)
57#endif
58
59/*----- Tokenisation tables -----------------------------------------------*
60 *
61 * These tables are from the BBC BASIC guide. Additional verification
62 * carried out on an A440 with RISC OS 3.1
63 */
64
992dc059 65static const char *tok_base[] = {
0398bb0d 66 "OTHERWISE",
67 "AND", "DIV", "EOR", "MOD", "OR", "ERROR", "LINE", "OFF",
68 "STEP", "SPC", "TAB(", "ELSE", "THEN", "*", "OPENIN", "PTR",
69 "PAGE", "TIME", "LOMEM", "HIMEM", "ABS", "ACS", "ADVAL", "ASC",
70 "ASN", "ATN", "BGET", "COS", "COUNT", "DEG", "ERL", "ERR",
71 "EVAL", "EXP", "EXT", "FALSE", "FN", "GET", "INKEY", "INSTR(",
72 "INT", "LEN", "LN", "LOG", "NOT", "OPENUP", "OPENOUT", "PI",
73 "POINT(", "POS", "RAD", "RND", "SGN", "SIN", "SQR", "TAN",
74 "TO", "TRUE", "USR", "VAL", "VPOS", "CHR$", "GET$", "INKEY$",
75 "LEFT$(", "MID$(", "RIGHT$(", "STR$", "STRING$(", "EOF", "*", "*",
76 "*", "WHEN", "OF", "ENDCASE", "ELSE", "ENDIF", "ENDWHILE", "PTR",
77 "PAGE", "TIME", "LOMEM", "HIMEM", "SOUND", "BPUT", "CALL", "CHAIN",
78 "CLEAR", "CLOSE", "CLG", "CLS", "DATA", "DEF", "DIM", "DRAW",
79 "END", "ENDPROC", "ENVELOPE", "FOR", "GOSUB", "GOTO", "GCOL", "IF",
80 "INPUT", "LET", "LOCAL", "MODE", "MOVE", "NEXT", "ON", "VDU",
81 "PLOT", "PRINT", "PROC", "READ", "REM", "REPEAT", "REPORT", "RESTORE",
82 "RETURN", "RUN", "STOP", "COLOUR", "TRACE", "UNTIL", "WIDTH", "OSCLI"
83};
84
992dc059 85static const char *tok_c6[] = {
0398bb0d 86 "SUM", "BEAT"
87};
88
992dc059 89static const char *tok_c7[] = {
0398bb0d 90 "APPEND", "AUTO",
91 "CRUNCH", "DELETE", "EDIT", "HELP", "LIST", "LOAD", "LVAR", "NEW",
92 "OLD", "RENUMBER", "SAVE", "TEXTLOAD", "TEXTSAVE", "TWIN", "TWINO",
93 "INSTALL"
94};
95
992dc059 96static const char *tok_c8[] = {
0398bb0d 97 "CASE", "CIRCLE",
98 "FILL", "ORIGIN", "POINT", "RECTANGLE", "SWAP", "WHILE", "WAIT", "MOUSE",
99 "QUIT", "SYS", "INSTALL", "LIBRARY", "TINT", "ELLIPSE", "BEATS", "TEMPO",
100 "VOICES", "VOICE", "STEREO", "OVERLAY"
101};
102
103#define ITEMS(array) (sizeof(array) / sizeof((array)[0]))
104
105/*----- Static variables --------------------------------------------------*/
106
107enum {
108 s_keyword, /* Expecting a keyword next */
109 s_normal, /* Normal state, reading input */
110 s_comment, /* In a command (or literal *cmd) */
111 s_quote, /* Inside a quoted string */
112 s_c6, s_c7, s_c8, /* Various shift states */
af9da5bb 113 s_linex, s_liney, s_linez, /* Line number states */
0398bb0d 114 s_dummy
115};
116
b98146e0 117static int state = s_normal; /* Current detokenisation state */
118static unsigned int lineno; /* Line number */
0398bb0d 119
120enum {
121 f_highlight = 1, /* Highlight keywords and things */
122 f_linenumbers = 2, /* Display linenumbers on left */
123 f_tty = 4, /* We're writing to TTY (or pager) */
124 f_less = 8, /* We're piping through `less' */
125 f_dummy
126};
127
b98146e0 128static int flags; /* Various options flags */
0398bb0d 129
130#ifdef HAVE_LIBTERMCAP
b98146e0 131static char termcap[2048]; /* Terminal capabilities buffer */
0398bb0d 132#endif
133
b98146e0 134static char *pager = 0; /* Pointer to pager to use */
0398bb0d 135
136/*----- Main code ---------------------------------------------------------*/
137
138/* --- @die@ --- *
139 *
140 * Arguments: @char *p@ = pointer to a string
141 *
142 * Returns: 1
143 *
144 * Use: Reports an error to the user and falls over flat on its face.
145 */
146
147static int die(const char *p)
148{
149 fprintf(stderr, "%s: %s\n", optprog, p);
150 return (1);
151}
152
b98146e0 153/* --- @keyword@ --- *
0398bb0d 154 *
155 * Arguments: @char *s@ = pointer to keyword string
156 * @FILE *fp@ = stream to write onto
157 *
158 * Returns: --
159 *
160 * Use: Displays a keyword in a nice way. There's some nasty hacking
161 * here to make GNU's `less' work properly. `more' appears to
162 * cope with highlighting codes OK, so that's fine. `less'
163 * prefers it if we attempt to `overstrike' the bolded
164 * characters. What fun...
165 */
166
b98146e0 167static void keyword(const char *s, FILE *fp)
0398bb0d 168{
169#ifdef HAVE_LIBTERMCAP
b98146e0 170 if ((~flags & (f_less | f_highlight)) == 0) {
0398bb0d 171 while (*s) {
172 putc(*s, fp);
b98146e0 173 putc('\b', fp);
0398bb0d 174 putc(*s, fp);
175 s++;
176 }
177 } else {
178 static char buff[24];
179 static char *hs, *he, *p = buff;
180
181 if (!hs) {
b98146e0 182 if (flags & f_highlight) {
0398bb0d 183 hs = tgetstr("md", &p);
184 he = tgetstr("me", &p);
185 } else
186 hs = he = "";
187 }
188 fputs(hs, fp);
189 fputs(s, fp);
190 fputs(he, fp);
191 }
192#else
193 fputs(s, fp);
194#endif
195}
196
b98146e0 197/* --- @mbtok@ --- *
0398bb0d 198 *
199 * Arguments: @int byte@ = the current byte
200 * @const char *t[]@ = pointer to token table
201 * @int n@ = number of items in token table
202 * @FILE *fp@ = stream to write onto
203 *
204 * Returns: 0 if everything's OK
205 *
206 * Use: Decodes multibyte tokens.
207 */
208
aa1ca0ec 209static int mbtok(int byte, const char *t[], int n, FILE *fp)
0398bb0d 210{
211 byte -= 0x8E;
212 if (byte >= n)
213 return (die("Bad program: invalid multibyte token"));
b98146e0 214 keyword(t[byte], fp);
215 state = s_normal;
0398bb0d 216 return (0);
217}
218
b98146e0 219/* --- @decode@ --- *
0398bb0d 220 *
221 * Arguments: @int byte@ = byte to decode
222 * @FILE *fp@ = stream to write onto
223 *
224 * Returns: 0 if everything's going OK
225 *
226 * Use: Decodes a byte, changing states as necessary.
227 */
228
aa1ca0ec 229static int decode(int byte, FILE *fp)
0398bb0d 230{
b98146e0 231 switch (state) {
af9da5bb 232
233 /* --- Tokenised states --- */
234
0398bb0d 235 case s_keyword:
236 if (byte == '*')
b98146e0 237 state = s_comment;
0398bb0d 238 else
b98146e0 239 state = s_normal;
0398bb0d 240 /* Fall through here */
af9da5bb 241
0398bb0d 242 case s_normal:
243 if (byte >= 0x7F) {
244 switch (byte) {
245 case 0xC6:
b98146e0 246 state = s_c6;
0398bb0d 247 break;
248 case 0xC7:
b98146e0 249 state = s_c7;
0398bb0d 250 break;
251 case 0xC8:
b98146e0 252 state = s_c8;
0398bb0d 253 break;
af9da5bb 254 case 0x8D:
b98146e0 255 state = s_linex;
af9da5bb 256 break;
0398bb0d 257 case 0x8B: /* ELSE */
258 case 0x8C: /* THEN */
259 case 0xF5: /* REPEAT (a funny one) */
b98146e0 260 state = s_keyword;
af9da5bb 261 goto keyword;
0398bb0d 262 break;
263 case 0xDC: /* DATA */
264 case 0xF4: /* REM */
b98146e0 265 state = s_comment;
0398bb0d 266 /* Fall through here */
267 default:
af9da5bb 268 keyword:
992dc059 269 keyword(tok_base[byte - 0x7F], fp);
0398bb0d 270 break;
271 }
272 } else {
273 if (byte == '"')
b98146e0 274 state = s_quote;
0398bb0d 275 fputc(byte, fp);
276 }
277 break;
af9da5bb 278
279 /* --- Non-tokenised states --- */
280
0398bb0d 281 case s_quote:
282 if (byte == '"')
b98146e0 283 state = s_normal;
0398bb0d 284 /* Fall through here */
af9da5bb 285
0398bb0d 286 case s_comment:
287 fputc(byte, fp);
288 break;
af9da5bb 289
290 /* --- Double-byte token states --- */
291
0398bb0d 292 case s_c6:
992dc059 293 return (mbtok(byte, tok_c6, ITEMS(tok_c6), fp));
0398bb0d 294 break;
af9da5bb 295
0398bb0d 296 case s_c7:
992dc059 297 return (mbtok(byte, tok_c7, ITEMS(tok_c7), fp));
0398bb0d 298 break;
af9da5bb 299
0398bb0d 300 case s_c8:
992dc059 301 return (mbtok(byte, tok_c8, ITEMS(tok_c8), fp));
0398bb0d 302 break;
af9da5bb 303
304 /* --- Encoded line number states --- */
305
306 case s_linex:
aa1ca0ec 307 byte ^= 0x54;
308 lineno = (((byte & 0x30) << 2) |
309 ((byte & 0x0c) << 12) |
310 ((byte & 0x03) << 16));
b98146e0 311 state++;
af9da5bb 312 break;
313
314 case s_liney:
aa1ca0ec 315 lineno |= byte & 0x3f;
b98146e0 316 state++;
af9da5bb 317 break;
318
319 case s_linez:
aa1ca0ec 320 lineno |= (byte & 0x3f) << 8;
b98146e0 321 fprintf(fp, "%u", lineno);
322 state = s_normal;
af9da5bb 323 break;
324
0398bb0d 325 }
326 return (0);
327}
328
b98146e0 329/* --- @line@ --- *
0398bb0d 330 *
331 * Arguments: @FILE *in@ = input stream to read
332 * @FILE *out@ = output stream to write
333 *
334 * Returns: Zero if there's another line after this one.
335 *
336 * Use: Decodes a BASIC line into stuff to be written.
337 */
338
b98146e0 339static int line(FILE *in, FILE *out)
0398bb0d 340{
341 /* --- Read the line number --- */
342
343 {
344 int a, b;
345
346 a = getc(in);
347 D( fprintf(stderr, "ln_0 == %i\n", a); )
348 if (a == EOF)
349 goto eof;
350 if (a == 0xFF)
351 return (1);
352
353 b = getc(in);
354 D( fprintf(stderr, "ln_1 == %i\n", b); )
355 if (b == EOF)
356 goto eof;
357
b98146e0 358 if (flags & f_linenumbers)
0398bb0d 359 fprintf(out, "%5i", (a << 8) + b);
360 }
361
362 {
363 int len;
364 int byte;
365
366 len = getc(in);
367 D( fprintf(stderr, "linelen == %i\n", len); )
368 if (len == EOF)
369 goto eof;
370 len -= 4;
371
aa1ca0ec 372 state = s_keyword;
0398bb0d 373 while (len) {
374 byte = getc(in);
af9da5bb 375 D( fprintf(stderr, "state == %i, byte == %i\n",
b98146e0 376 state, byte); )
0398bb0d 377 if (byte == EOF)
378 goto eof;
b98146e0 379 decode(byte, out);
0398bb0d 380 len--;
381 }
382 putc('\n', out);
383
384 byte = getc(in);
385 D( fprintf(stderr, "eol == %i\n", byte); )
386 if (byte == EOF)
387 goto eof;
388 else if (byte != 0x0D)
389 return (die("Bad program: expected end-of-line delimiter"));
390 }
391
392 return (0);
393
394eof:
395 return (die("Bad program: unexpected end-of-file"));
396}
397
b98146e0 398/* --- @file@ --- *
0398bb0d 399 *
400 * Arguments: @FILE *in@ = the input stream
401 * @FILE *out@ = the output stream
402 *
403 * Returns: --
404 *
405 * Use: Decodes an entire file.
406 */
407
b98146e0 408static void file(FILE *in, FILE *out)
0398bb0d 409{
410 int byte;
411
412 /* --- Check for the inital newline char --- */
413
414 byte = getc(in);
415 if (byte != 0x0D)
416 die("Bad program: doesn't start with a newline");
417
418 /* --- Now read the lines in one by one --- */
419
b98146e0 420 while (!line(in, out)) ;
0398bb0d 421
422 /* --- Check that we're really at end-of-file --- */
423
424 byte = getc(in);
425 if (byte != EOF)
426 die("Found data after end of program");
427}
428
992dc059 429/* --- @sig_pipe@ --- *
0398bb0d 430 *
431 * Arguments: @int s@ = signal number
432 *
433 * Returns: Doesn't
434 *
435 * Use: Handles SIGPIPE signals, and gracefully kills the program.
436 */
437
992dc059 438static void sig_pipe(int s)
0398bb0d 439{
440 (void) s;
441 exit(0); /* Gracefully, oh yes */
442}
443
b98146e0 444/* --- @options@ --- *
0398bb0d 445 *
446 * Arguments: @int c@ = number of arguments
447 * @char *v[]@ = pointer to arguments
448 * @char *s@ = pointer to short options
449 * @struct option *o@ = pointer to long options
450 *
451 * Returns: --
452 *
453 * Use: Parses lots of arguments.
454 */
455
b98146e0 456static void options(int c, char *v[], const char *s,
0398bb0d 457 const struct option *o)
458{
459 int i;
460
461 for (;;) {
b98146e0 462 i = mdwopt(c, v, s, o, 0, 0, OPTF_NEGATION | OPTF_ENVVAR);
0398bb0d 463 if (i == -1)
464 break;
465
466 switch (i) {
467 case 'v':
468 case 'h':
b98146e0 469 printf("%s version " VERSION "\n", optprog);
0398bb0d 470 if (i == 'v')
471 exit(0);
472 printf("\n"
473 "%s [-hv] [-n|+n] [-l|+l] [-p PAGER] [file...]\n"
474 "\n"
475 "Types BBC BASIC programs in a readable way. Options "
476 "currently supported are as\n"
477 "follows:\n"
478 "\n"
479 "-h, --help: Displays this evil help message\n"
480 "-v, --version: Displays the current version number\n"
481 "-n, --line-numbers: Displays line numbers for each line\n"
482 "-l, --highlight: Attempts to highlight keywords\n"
483 "-p, --pager=PAGER: Sets pager to use (default $PAGER)\n"
484 "\n"
485 "Prefix long options with `no-' to cancel them. Use `+' to "
486 "cancel short options.\n"
487 "Options can also be placed in the `BASCAT' environment "
488 "variable, if you don't\n"
489 "like the standard settings.\n",
490 optprog);
491 exit(0);
492 break;
493 case 'n':
b98146e0 494 flags |= f_linenumbers;
0398bb0d 495 break;
b98146e0 496 case 'n' | OPTF_NEGATED:
497 flags &= ~f_linenumbers;
0398bb0d 498 break;
499 case 'l':
b98146e0 500 flags |= f_highlight;
0398bb0d 501 break;
b98146e0 502 case 'l' | OPTF_NEGATED:
503 flags &= ~f_highlight;
0398bb0d 504 break;
505 case 'p':
b98146e0 506 pager = optarg;
0398bb0d 507 break;
508 }
509 }
510}
511
512/* --- @main@ --- *
513 *
514 * Arguments: @int argc@ = number of arguments
515 * @char *argc[]@ = pointer to what the arguments are
516 *
517 * Returns: 0 if it all worked
518 *
519 * Use: Displays BASIC programs.
520 */
521
522int main(int argc, char *argv[])
523{
524 static struct option opts[] = {
b98146e0 525 { "help", 0, 0, 'h' },
526 { "version", 0, 0, 'v' },
527 { "line-numbers", OPTF_NEGATE, 0, 'n' },
528 { "highlight", OPTF_NEGATE, 0, 'l' },
529 { "pager", OPTF_ARGREQ, 0, 'p' },
530 { 0, 0, 0, 0 }
0398bb0d 531 };
532 static char *shortopts = "hvn+l+p:";
533
534 /* --- Parse the command line options --- */
535
b98146e0 536 options(argc, argv, shortopts, opts);
0398bb0d 537
538 /* --- Now do the job --- */
539
540 if (optind == argc && isatty(0)) {
541 fprintf(stderr,
542 "%s: No filenames given, and standard input is a tty\n"
543 "To force reading from stdin, use `%s -'. For help, type "
544 "`%s --help'.\n",
545 optprog, optprog, optprog);
546 exit(0);
547 }
548
549#ifdef HAVE_LIBTERMCAP
b98146e0 550 if (flags & f_highlight)
551 tgetent(termcap, getenv("TERM"));
0398bb0d 552#endif
553
554 {
555 FILE *in;
556 FILE *out;
557
558 /* --- If output is to a terminal, try paging --- *
559 *
560 * All programs which spew text should do this ;-)
561 */
562
563 if (isatty(1)) {
b98146e0 564 if (!pager)
565 pager = getenv("PAGER");
566 if (!pager)
567 pager = PAGER; /* Worth a try */
568 if (strstr(pager, "less"))
569 flags |= f_less; /* HACK!!! */
570 out = popen(pager, "w");
0398bb0d 571 if (!out)
572 out = stdout;
573 else {
b98146e0 574 flags |= f_tty;
992dc059 575 signal(SIGPIPE, sig_pipe);
0398bb0d 576 }
577 } else
578 out = stdout;
579
580 /* --- Now go through all the files --- */
581
582 if (optind == argc)
b98146e0 583 file(stdin, out);
0398bb0d 584 else
585 while (optind < argc) {
586 if (strcmp(argv[optind], "-") == 0)
b98146e0 587 file(stdin, out);
0398bb0d 588 else {
589 in = fopen(argv[optind], "rb");
590 if (!in) {
591 fprintf(stderr,
592 "%s: Couldn't open input file: %s\n",
593 optprog, strerror(errno));
594 } else {
b98146e0 595 file(in, out);
0398bb0d 596 fclose(in);
597 }
598 }
599 optind++;
600 }
b98146e0 601 if (flags & f_tty)
0398bb0d 602 pclose(out);
603 }
604
605 return (0);
606}
607
608/*----- That's all, folks -------------------------------------------------*/