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