b685a5cd0ff393665b2ffbf67b816c4e6cb9c05b
3 * $Id: bascat.c,v 1.4 1999/10/28 10:42:23 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.4 1999/10/28 10:42:23 mdw
33 * More minor twiddling.
35 * Revision 1.3 1999/10/28 10:18:17 mdw
36 * Minor name changes for new coding standards.
38 * Revision 1.2 1998/06/26 11:29:28 mdw
39 * Added support for line-numbers.
41 * Revision 1.1 1998/03/16 15:21:37 mdw
42 * Files placed under CVS control.
44 * Revision 1.1 1997/07/23 01:19:33 mdw
49 /*----- Header files ------------------------------------------------------*/
51 /* --- ANSI library headers --- */
60 /* --- Operating system specific headers --- */
62 #ifdef HAVE_LIBTERMCAP
67 /* --- Private headers --- */
71 /*----- Version information -----------------------------------------------*/
79 /*----- Tokenisation tables -----------------------------------------------*
81 * These tables are from the BBC BASIC guide. Additional verification
82 * carried out on an A440 with RISC OS 3.1
85 static const char *tok_base
[] = {
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"
105 static const char *tok_c6
[] = {
109 static const char *tok_c7
[] = {
111 "CRUNCH", "DELETE", "EDIT", "HELP", "LIST", "LOAD", "LVAR", "NEW",
112 "OLD", "RENUMBER", "SAVE", "TEXTLOAD", "TEXTSAVE", "TWIN", "TWINO",
116 static const char *tok_c8
[] = {
118 "FILL", "ORIGIN", "POINT", "RECTANGLE", "SWAP", "WHILE", "WAIT", "MOUSE",
119 "QUIT", "SYS", "INSTALL", "LIBRARY", "TINT", "ELLIPSE", "BEATS", "TEMPO",
120 "VOICES", "VOICE", "STEREO", "OVERLAY"
123 #define ITEMS(array) (sizeof(array) / sizeof((array)[0]))
125 /*----- Static variables --------------------------------------------------*/
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 */
137 static int state
= s_normal
; /* Current detokenisation state */
138 static unsigned int lineno
; /* Line number */
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' */
148 static int flags
; /* Various options flags */
150 #ifdef HAVE_LIBTERMCAP
151 static char termcap
[2048]; /* Terminal capabilities buffer */
154 static char *pager
= 0; /* Pointer to pager to use */
156 /*----- Main code ---------------------------------------------------------*/
160 * Arguments: @char *p@ = pointer to a string
164 * Use: Reports an error to the user and falls over flat on its face.
167 static int die(const char *p
)
169 fprintf(stderr
, "%s: %s\n", optprog
, p
);
173 /* --- @keyword@ --- *
175 * Arguments: @char *s@ = pointer to keyword string
176 * @FILE *fp@ = stream to write onto
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...
187 static void keyword(const char *s
, FILE *fp
)
189 #ifdef HAVE_LIBTERMCAP
190 if ((~flags
& (f_less
| f_highlight
)) == 0) {
198 static char buff
[24];
199 static char *hs
, *he
, *p
= buff
;
202 if (flags
& f_highlight
) {
203 hs
= tgetstr("md", &p
);
204 he
= tgetstr("me", &p
);
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
224 * Returns: 0 if everything's OK
226 * Use: Decodes multibyte tokens.
229 static int mbtok(int byte
, const char *t
[], int n
, FILE * fp
)
233 return (die("Bad program: invalid multibyte token"));
234 keyword(t
[byte
], fp
);
239 /* --- @decode@ --- *
241 * Arguments: @int byte@ = byte to decode
242 * @FILE *fp@ = stream to write onto
244 * Returns: 0 if everything's going OK
246 * Use: Decodes a byte, changing states as necessary.
249 static int decode(int byte
, FILE * fp
)
253 /* --- Tokenised states --- */
260 /* Fall through here */
277 case 0x8B: /* ELSE */
278 case 0x8C: /* THEN */
279 case 0xF5: /* REPEAT (a funny one) */
283 case 0xDC: /* DATA */
286 /* Fall through here */
289 keyword(tok_base
[byte
- 0x7F], fp
);
299 /* --- Non-tokenised states --- */
304 /* Fall through here */
310 /* --- Double-byte token states --- */
313 return (mbtok(byte
, tok_c6
, ITEMS(tok_c6
), fp
));
317 return (mbtok(byte
, tok_c7
, ITEMS(tok_c7
), fp
));
321 return (mbtok(byte
, tok_c8
, ITEMS(tok_c8
), fp
));
324 /* --- Encoded line number states --- */
327 lineno
= ((((byte
<< 2) | (byte
<< 12)) & 0xc0c0u
) ^ 0x4040u
);
332 lineno
|= byte
& 0x3fu
;
337 lineno
|= (byte
<< 8) & 0x3fu
;
338 fprintf(fp
, "%u", lineno
);
348 * Arguments: @FILE *in@ = input stream to read
349 * @FILE *out@ = output stream to write
351 * Returns: Zero if there's another line after this one.
353 * Use: Decodes a BASIC line into stuff to be written.
356 static int line(FILE *in
, FILE *out
)
358 /* --- Read the line number --- */
364 D( fprintf(stderr
, "ln_0 == %i\n", a
); )
371 D( fprintf(stderr
, "ln_1 == %i\n", b
); )
375 if (flags
& f_linenumbers
)
376 fprintf(out
, "%5i", (a
<< 8) + b
);
384 D( fprintf(stderr
, "linelen == %i\n", len
); )
392 D( fprintf(stderr
, "state == %i, byte == %i\n",
402 D( fprintf(stderr
, "eol == %i\n", byte
); )
405 else if (byte
!= 0x0D)
406 return (die("Bad program: expected end-of-line delimiter"));
412 return (die("Bad program: unexpected end-of-file"));
417 * Arguments: @FILE *in@ = the input stream
418 * @FILE *out@ = the output stream
422 * Use: Decodes an entire file.
425 static void file(FILE *in
, FILE *out
)
429 /* --- Check for the inital newline char --- */
433 die("Bad program: doesn't start with a newline");
435 /* --- Now read the lines in one by one --- */
437 while (!line(in
, out
)) ;
439 /* --- Check that we're really at end-of-file --- */
443 die("Found data after end of program");
446 /* --- @sig_pipe@ --- *
448 * Arguments: @int s@ = signal number
452 * Use: Handles SIGPIPE signals, and gracefully kills the program.
455 static void sig_pipe(int s
)
458 exit(0); /* Gracefully, oh yes */
461 /* --- @options@ --- *
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
470 * Use: Parses lots of arguments.
473 static void options(int c
, char *v
[], const char *s
,
474 const struct option
*o
)
479 i
= mdwopt(c
, v
, s
, o
, 0, 0, OPTF_NEGATION
| OPTF_ENVVAR
);
486 printf("%s version " VERSION
"\n", optprog
);
490 "%s [-hv] [-n|+n] [-l|+l] [-p PAGER] [file...]\n"
492 "Types BBC BASIC programs in a readable way. Options "
493 "currently supported are as\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"
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",
511 flags
|= f_linenumbers
;
513 case 'n' | OPTF_NEGATED
:
514 flags
&= ~f_linenumbers
;
517 flags
|= f_highlight
;
519 case 'l' | OPTF_NEGATED
:
520 flags
&= ~f_highlight
;
531 * Arguments: @int argc@ = number of arguments
532 * @char *argc[]@ = pointer to what the arguments are
534 * Returns: 0 if it all worked
536 * Use: Displays BASIC programs.
539 int main(int argc
, char *argv
[])
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' },
549 static char *shortopts
= "hvn+l+p:";
551 /* --- Parse the command line options --- */
553 options(argc
, argv
, shortopts
, opts
);
555 /* --- Now do the job --- */
557 if (optind
== argc
&& isatty(0)) {
559 "%s: No filenames given, and standard input is a tty\n"
560 "To force reading from stdin, use `%s -'. For help, type "
562 optprog
, optprog
, optprog
);
566 #ifdef HAVE_LIBTERMCAP
567 if (flags
& f_highlight
)
568 tgetent(termcap
, getenv("TERM"));
575 /* --- If output is to a terminal, try paging --- *
577 * All programs which spew text should do this ;-)
582 pager
= getenv("PAGER");
584 pager
= PAGER
; /* Worth a try */
585 if (strstr(pager
, "less"))
586 flags
|= f_less
; /* HACK!!! */
587 out
= popen(pager
, "w");
592 signal(SIGPIPE
, sig_pipe
);
597 /* --- Now go through all the files --- */
602 while (optind
< argc
) {
603 if (strcmp(argv
[optind
], "-") == 0)
606 in
= fopen(argv
[optind
], "rb");
609 "%s: Couldn't open input file: %s\n",
610 optprog
, strerror(errno
));
625 /*----- That's all, folks -------------------------------------------------*/