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