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