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