3 * $Id: bascat.c,v 1.5 1999/11/25 01:08:57 mdw Exp $
5 * Display BBC BASIC programs more or less anywhere
7 * (c) 1996, 1997 Matthew Wilcox and Mark Wooding
10 /*----- Licensing notice --------------------------------------------------*
12 * This file is part of Bascat.
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.
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.
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.
29 /*----- Revision history --------------------------------------------------*
32 * Revision 1.5 1999/11/25 01:08:57 mdw
33 * Fix decoding of line numbers.
35 * Revision 1.4 1999/10/28 10:42:23 mdw
36 * More minor twiddling.
38 * Revision 1.3 1999/10/28 10:18:17 mdw
39 * Minor name changes for new coding standards.
41 * Revision 1.2 1998/06/26 11:29:28 mdw
42 * Added support for line-numbers.
44 * Revision 1.1 1998/03/16 15:21:37 mdw
45 * Files placed under CVS control.
47 * Revision 1.1 1997/07/23 01:19:33 mdw
52 /*----- Header files ------------------------------------------------------*/
54 /* --- ANSI library headers --- */
63 /* --- Operating system specific headers --- */
65 #ifdef HAVE_LIBTERMCAP
70 /* --- Private headers --- */
74 /*----- Version information -----------------------------------------------*/
82 /*----- Tokenisation tables -----------------------------------------------*
84 * These tables are from the BBC BASIC guide. Additional verification
85 * carried out on an A440 with RISC OS 3.1
88 static const char *tok_base
[] = {
90 "AND", "DIV", "EOR", "MOD", "OR", "ERROR", "LINE", "OFF",
91 "STEP", "SPC", "TAB(", "ELSE", "THEN", "*", "OPENIN", "PTR",
92 "PAGE", "TIME", "LOMEM", "HIMEM", "ABS", "ACS", "ADVAL", "ASC",
93 "ASN", "ATN", "BGET", "COS", "COUNT", "DEG", "ERL", "ERR",
94 "EVAL", "EXP", "EXT", "FALSE", "FN", "GET", "INKEY", "INSTR(",
95 "INT", "LEN", "LN", "LOG", "NOT", "OPENUP", "OPENOUT", "PI",
96 "POINT(", "POS", "RAD", "RND", "SGN", "SIN", "SQR", "TAN",
97 "TO", "TRUE", "USR", "VAL", "VPOS", "CHR$", "GET$", "INKEY$",
98 "LEFT$(", "MID$(", "RIGHT$(", "STR$", "STRING$(", "EOF", "*", "*",
99 "*", "WHEN", "OF", "ENDCASE", "ELSE", "ENDIF", "ENDWHILE", "PTR",
100 "PAGE", "TIME", "LOMEM", "HIMEM", "SOUND", "BPUT", "CALL", "CHAIN",
101 "CLEAR", "CLOSE", "CLG", "CLS", "DATA", "DEF", "DIM", "DRAW",
102 "END", "ENDPROC", "ENVELOPE", "FOR", "GOSUB", "GOTO", "GCOL", "IF",
103 "INPUT", "LET", "LOCAL", "MODE", "MOVE", "NEXT", "ON", "VDU",
104 "PLOT", "PRINT", "PROC", "READ", "REM", "REPEAT", "REPORT", "RESTORE",
105 "RETURN", "RUN", "STOP", "COLOUR", "TRACE", "UNTIL", "WIDTH", "OSCLI"
108 static const char *tok_c6
[] = {
112 static const char *tok_c7
[] = {
114 "CRUNCH", "DELETE", "EDIT", "HELP", "LIST", "LOAD", "LVAR", "NEW",
115 "OLD", "RENUMBER", "SAVE", "TEXTLOAD", "TEXTSAVE", "TWIN", "TWINO",
119 static const char *tok_c8
[] = {
121 "FILL", "ORIGIN", "POINT", "RECTANGLE", "SWAP", "WHILE", "WAIT", "MOUSE",
122 "QUIT", "SYS", "INSTALL", "LIBRARY", "TINT", "ELLIPSE", "BEATS", "TEMPO",
123 "VOICES", "VOICE", "STEREO", "OVERLAY"
126 #define ITEMS(array) (sizeof(array) / sizeof((array)[0]))
128 /*----- Static variables --------------------------------------------------*/
131 s_keyword
, /* Expecting a keyword next */
132 s_normal
, /* Normal state, reading input */
133 s_comment
, /* In a command (or literal *cmd) */
134 s_quote
, /* Inside a quoted string */
135 s_c6
, s_c7
, s_c8
, /* Various shift states */
136 s_linex
, s_liney
, s_linez
, /* Line number states */
140 static int state
= s_normal
; /* Current detokenisation state */
141 static unsigned int lineno
; /* Line number */
144 f_highlight
= 1, /* Highlight keywords and things */
145 f_linenumbers
= 2, /* Display linenumbers on left */
146 f_tty
= 4, /* We're writing to TTY (or pager) */
147 f_less
= 8, /* We're piping through `less' */
151 static int flags
; /* Various options flags */
153 #ifdef HAVE_LIBTERMCAP
154 static char termcap
[2048]; /* Terminal capabilities buffer */
157 static char *pager
= 0; /* Pointer to pager to use */
159 /*----- Main code ---------------------------------------------------------*/
163 * Arguments: @char *p@ = pointer to a string
167 * Use: Reports an error to the user and falls over flat on its face.
170 static int die(const char *p
)
172 fprintf(stderr
, "%s: %s\n", optprog
, p
);
176 /* --- @keyword@ --- *
178 * Arguments: @char *s@ = pointer to keyword string
179 * @FILE *fp@ = stream to write onto
183 * Use: Displays a keyword in a nice way. There's some nasty hacking
184 * here to make GNU's `less' work properly. `more' appears to
185 * cope with highlighting codes OK, so that's fine. `less'
186 * prefers it if we attempt to `overstrike' the bolded
187 * characters. What fun...
190 static void keyword(const char *s
, FILE *fp
)
192 #ifdef HAVE_LIBTERMCAP
193 if ((~flags
& (f_less
| f_highlight
)) == 0) {
201 static char buff
[24];
202 static char *hs
, *he
, *p
= buff
;
205 if (flags
& f_highlight
) {
206 hs
= tgetstr("md", &p
);
207 he
= tgetstr("me", &p
);
222 * Arguments: @int byte@ = the current byte
223 * @const char *t[]@ = pointer to token table
224 * @int n@ = number of items in token table
225 * @FILE *fp@ = stream to write onto
227 * Returns: 0 if everything's OK
229 * Use: Decodes multibyte tokens.
232 static int mbtok(int byte
, const char *t
[], int n
, FILE *fp
)
236 return (die("Bad program: invalid multibyte token"));
237 keyword(t
[byte
], fp
);
242 /* --- @decode@ --- *
244 * Arguments: @int byte@ = byte to decode
245 * @FILE *fp@ = stream to write onto
247 * Returns: 0 if everything's going OK
249 * Use: Decodes a byte, changing states as necessary.
252 static int decode(int byte
, FILE *fp
)
256 /* --- Tokenised states --- */
263 /* Fall through here */
280 case 0x8B: /* ELSE */
281 case 0x8C: /* THEN */
282 case 0xF5: /* REPEAT (a funny one) */
286 case 0xDC: /* DATA */
289 /* Fall through here */
292 keyword(tok_base
[byte
- 0x7F], fp
);
302 /* --- Non-tokenised states --- */
307 /* Fall through here */
313 /* --- Double-byte token states --- */
316 return (mbtok(byte
, tok_c6
, ITEMS(tok_c6
), fp
));
320 return (mbtok(byte
, tok_c7
, ITEMS(tok_c7
), fp
));
324 return (mbtok(byte
, tok_c8
, ITEMS(tok_c8
), fp
));
327 /* --- Encoded line number states --- */
331 lineno
= (((byte
& 0x30) << 2) |
332 ((byte
& 0x0c) << 12) |
333 ((byte
& 0x03) << 16));
338 lineno
|= byte
& 0x3f;
343 lineno
|= (byte
& 0x3f) << 8;
344 fprintf(fp
, "%u", lineno
);
354 * Arguments: @FILE *in@ = input stream to read
355 * @FILE *out@ = output stream to write
357 * Returns: Zero if there's another line after this one.
359 * Use: Decodes a BASIC line into stuff to be written.
362 static int line(FILE *in
, FILE *out
)
364 /* --- Read the line number --- */
370 D( fprintf(stderr
, "ln_0 == %i\n", a
); )
377 D( fprintf(stderr
, "ln_1 == %i\n", b
); )
381 if (flags
& f_linenumbers
)
382 fprintf(out
, "%5i", (a
<< 8) + b
);
390 D( fprintf(stderr
, "linelen == %i\n", len
); )
398 D( fprintf(stderr
, "state == %i, byte == %i\n",
408 D( fprintf(stderr
, "eol == %i\n", byte
); )
411 else if (byte
!= 0x0D)
412 return (die("Bad program: expected end-of-line delimiter"));
418 return (die("Bad program: unexpected end-of-file"));
423 * Arguments: @FILE *in@ = the input stream
424 * @FILE *out@ = the output stream
428 * Use: Decodes an entire file.
431 static void file(FILE *in
, FILE *out
)
435 /* --- Check for the inital newline char --- */
439 die("Bad program: doesn't start with a newline");
441 /* --- Now read the lines in one by one --- */
443 while (!line(in
, out
)) ;
445 /* --- Check that we're really at end-of-file --- */
449 die("Found data after end of program");
452 /* --- @sig_pipe@ --- *
454 * Arguments: @int s@ = signal number
458 * Use: Handles SIGPIPE signals, and gracefully kills the program.
461 static void sig_pipe(int s
)
464 exit(0); /* Gracefully, oh yes */
467 /* --- @options@ --- *
469 * Arguments: @int c@ = number of arguments
470 * @char *v[]@ = pointer to arguments
471 * @char *s@ = pointer to short options
472 * @struct option *o@ = pointer to long options
476 * Use: Parses lots of arguments.
479 static void options(int c
, char *v
[], const char *s
,
480 const struct option
*o
)
485 i
= mdwopt(c
, v
, s
, o
, 0, 0, OPTF_NEGATION
| OPTF_ENVVAR
);
492 printf("%s version " VERSION
"\n", optprog
);
496 "%s [-hv] [-n|+n] [-l|+l] [-p PAGER] [file...]\n"
498 "Types BBC BASIC programs in a readable way. Options "
499 "currently supported are as\n"
502 "-h, --help: Displays this evil help message\n"
503 "-v, --version: Displays the current version number\n"
504 "-n, --line-numbers: Displays line numbers for each line\n"
505 "-l, --highlight: Attempts to highlight keywords\n"
506 "-p, --pager=PAGER: Sets pager to use (default $PAGER)\n"
508 "Prefix long options with `no-' to cancel them. Use `+' to "
509 "cancel short options.\n"
510 "Options can also be placed in the `BASCAT' environment "
511 "variable, if you don't\n"
512 "like the standard settings.\n",
517 flags
|= f_linenumbers
;
519 case 'n' | OPTF_NEGATED
:
520 flags
&= ~f_linenumbers
;
523 flags
|= f_highlight
;
525 case 'l' | OPTF_NEGATED
:
526 flags
&= ~f_highlight
;
537 * Arguments: @int argc@ = number of arguments
538 * @char *argc[]@ = pointer to what the arguments are
540 * Returns: 0 if it all worked
542 * Use: Displays BASIC programs.
545 int main(int argc
, char *argv
[])
547 static struct option opts
[] = {
548 { "help", 0, 0, 'h' },
549 { "version", 0, 0, 'v' },
550 { "line-numbers", OPTF_NEGATE
, 0, 'n' },
551 { "highlight", OPTF_NEGATE
, 0, 'l' },
552 { "pager", OPTF_ARGREQ
, 0, 'p' },
555 static char *shortopts
= "hvn+l+p:";
557 /* --- Parse the command line options --- */
559 options(argc
, argv
, shortopts
, opts
);
561 /* --- Now do the job --- */
563 if (optind
== argc
&& isatty(0)) {
565 "%s: No filenames given, and standard input is a tty\n"
566 "To force reading from stdin, use `%s -'. For help, type "
568 optprog
, optprog
, optprog
);
572 #ifdef HAVE_LIBTERMCAP
573 if (flags
& f_highlight
)
574 tgetent(termcap
, getenv("TERM"));
581 /* --- If output is to a terminal, try paging --- *
583 * All programs which spew text should do this ;-)
588 pager
= getenv("PAGER");
590 pager
= PAGER
; /* Worth a try */
591 if (strstr(pager
, "less"))
592 flags
|= f_less
; /* HACK!!! */
593 out
= popen(pager
, "w");
598 signal(SIGPIPE
, sig_pipe
);
603 /* --- Now go through all the files --- */
608 while (optind
< argc
) {
609 if (strcmp(argv
[optind
], "-") == 0)
612 in
= fopen(argv
[optind
], "rb");
615 "%s: Couldn't open input file: %s\n",
616 optprog
, strerror(errno
));
631 /*----- That's all, folks -------------------------------------------------*/