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