chiark / gitweb /
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 -------------------------------------------------*/