3 * $Id: bascat.c,v 1.2 1998/06/26 11:29:28 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.2 1998/06/26 11:29:28 mdw
33 * Added support for line-numbers.
35 * Revision 1.1 1998/03/16 15:21:37 mdw
36 * Files placed under CVS control.
38 * Revision 1.1 1997/07/23 01:19:33 mdw
43 /*----- Header files ------------------------------------------------------*/
45 /* --- ANSI library headers --- */
54 /* --- Operating system specific headers --- */
56 #ifdef HAVE_LIBTERMCAP
61 /* --- Private headers --- */
65 /*----- Version information -----------------------------------------------*/
73 /*----- Tokenisation tables -----------------------------------------------*
75 * These tables are from the BBC BASIC guide. Additional verification
76 * carried out on an A440 with RISC OS 3.1
79 static const char *bcTok__base
[] = {
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"
99 static const char *bcTok__c6
[] = {
103 static const char *bcTok__c7
[] = {
105 "CRUNCH", "DELETE", "EDIT", "HELP", "LIST", "LOAD", "LVAR", "NEW",
106 "OLD", "RENUMBER", "SAVE", "TEXTLOAD", "TEXTSAVE", "TWIN", "TWINO",
110 static const char *bcTok__c8
[] = {
112 "FILL", "ORIGIN", "POINT", "RECTANGLE", "SWAP", "WHILE", "WAIT", "MOUSE",
113 "QUIT", "SYS", "INSTALL", "LIBRARY", "TINT", "ELLIPSE", "BEATS", "TEMPO",
114 "VOICES", "VOICE", "STEREO", "OVERLAY"
117 #define ITEMS(array) (sizeof(array) / sizeof((array)[0]))
119 /*----- Static variables --------------------------------------------------*/
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 */
131 static int bc__state
= s_normal
; /* Current detokenisation state */
132 static unsigned int bc__lineno
; /* Line number */
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' */
142 static int bc__flags
; /* Various options flags */
144 #ifdef HAVE_LIBTERMCAP
145 static char bc__termcap
[2048]; /* Terminal capabilities buffer */
148 static char *bc__pager
= 0; /* Pointer to pager to use */
150 /*----- Main code ---------------------------------------------------------*/
154 * Arguments: @char *p@ = pointer to a string
158 * Use: Reports an error to the user and falls over flat on its face.
161 static int die(const char *p
)
163 fprintf(stderr
, "%s: %s\n", optprog
, p
);
167 /* --- @bc__keyword@ --- *
169 * Arguments: @char *s@ = pointer to keyword string
170 * @FILE *fp@ = stream to write onto
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...
181 static void bc__keyword(const char *s
, FILE *fp
)
183 #ifdef HAVE_LIBTERMCAP
184 if ((~bc__flags
& (f_less
| f_highlight
)) == 0) {
187 putc(8, fp
); /* evil... */
192 static char buff
[24];
193 static char *hs
, *he
, *p
= buff
;
196 if (bc__flags
& f_highlight
) {
197 hs
= tgetstr("md", &p
);
198 he
= tgetstr("me", &p
);
211 /* --- @bc__mbtok@ --- *
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
218 * Returns: 0 if everything's OK
220 * Use: Decodes multibyte tokens.
223 static int bc__mbtok(int byte
, const char *t
[], int n
, FILE * fp
)
227 return (die("Bad program: invalid multibyte token"));
228 bc__keyword(t
[byte
], fp
);
229 bc__state
= s_normal
;
233 /* --- @bc__decode@ --- *
235 * Arguments: @int byte@ = byte to decode
236 * @FILE *fp@ = stream to write onto
238 * Returns: 0 if everything's going OK
240 * Use: Decodes a byte, changing states as necessary.
243 static int bc__decode(int byte
, FILE * fp
)
247 /* --- Tokenised states --- */
251 bc__state
= s_comment
;
253 bc__state
= s_normal
;
254 /* Fall through here */
271 case 0x8B: /* ELSE */
272 case 0x8C: /* THEN */
273 case 0xF5: /* REPEAT (a funny one) */
274 bc__state
= s_keyword
;
277 case 0xDC: /* DATA */
279 bc__state
= s_comment
;
280 /* Fall through here */
283 bc__keyword(bcTok__base
[byte
- 0x7F], fp
);
293 /* --- Non-tokenised states --- */
297 bc__state
= s_normal
;
298 /* Fall through here */
304 /* --- Double-byte token states --- */
307 return (bc__mbtok(byte
, bcTok__c6
, ITEMS(bcTok__c6
), fp
));
311 return (bc__mbtok(byte
, bcTok__c7
, ITEMS(bcTok__c7
), fp
));
315 return (bc__mbtok(byte
, bcTok__c8
, ITEMS(bcTok__c8
), fp
));
318 /* --- Encoded line number states --- */
321 bc__lineno
= ((((byte
<< 2) | (byte
<< 12)) & 0xc0c0u
) ^ 0x4040u
);
326 bc__lineno
|= byte
& 0x3fu
;
331 bc__lineno
|= (byte
<< 8) & 0x3fu
;
332 fprintf(fp
, "%u", bc__lineno
);
333 bc__state
= s_normal
;
340 /* --- @bc__line@ --- *
342 * Arguments: @FILE *in@ = input stream to read
343 * @FILE *out@ = output stream to write
345 * Returns: Zero if there's another line after this one.
347 * Use: Decodes a BASIC line into stuff to be written.
350 static int bc__line(FILE *in
, FILE *out
)
352 /* --- Read the line number --- */
358 D( fprintf(stderr
, "ln_0 == %i\n", a
); )
365 D( fprintf(stderr
, "ln_1 == %i\n", b
); )
369 if (bc__flags
& f_linenumbers
)
370 fprintf(out
, "%5i", (a
<< 8) + b
);
378 D( fprintf(stderr
, "linelen == %i\n", len
); )
383 bc__state
= s_normal
;
386 D( fprintf(stderr
, "state == %i, byte == %i\n",
390 bc__decode(byte
, out
);
396 D( fprintf(stderr
, "eol == %i\n", byte
); )
399 else if (byte
!= 0x0D)
400 return (die("Bad program: expected end-of-line delimiter"));
406 return (die("Bad program: unexpected end-of-file"));
409 /* --- @bc__file@ --- *
411 * Arguments: @FILE *in@ = the input stream
412 * @FILE *out@ = the output stream
416 * Use: Decodes an entire file.
419 static void bc__file(FILE *in
, FILE *out
)
423 /* --- Check for the inital newline char --- */
427 die("Bad program: doesn't start with a newline");
429 /* --- Now read the lines in one by one --- */
431 while (!bc__line(in
, out
)) ;
433 /* --- Check that we're really at end-of-file --- */
437 die("Found data after end of program");
440 /* --- @bc__sigPipe@ --- *
442 * Arguments: @int s@ = signal number
446 * Use: Handles SIGPIPE signals, and gracefully kills the program.
449 static void bc__sigPipe(int s
)
452 exit(0); /* Gracefully, oh yes */
455 /* --- @bc__options@ --- *
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
464 * Use: Parses lots of arguments.
467 static void bc__options(int c
, char *v
[], const char *s
,
468 const struct option
*o
)
473 i
= mdwopt(c
, v
, s
, o
, 0, 0, gFlag_negation
| gFlag_envVar
);
480 printf("%s v. " VERSION
" (" __DATE__
")\n", optprog
);
484 "%s [-hv] [-n|+n] [-l|+l] [-p PAGER] [file...]\n"
486 "Types BBC BASIC programs in a readable way. Options "
487 "currently supported are as\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"
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",
505 bc__flags
|= f_linenumbers
;
507 case 'n' | gFlag_negated
:
508 bc__flags
&= ~f_linenumbers
;
511 bc__flags
|= f_highlight
;
513 case 'l' | gFlag_negated
:
514 bc__flags
&= ~f_highlight
;
525 * Arguments: @int argc@ = number of arguments
526 * @char *argc[]@ = pointer to what the arguments are
528 * Returns: 0 if it all worked
530 * Use: Displays BASIC programs.
533 int main(int argc
, char *argv
[])
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' },
543 static char *shortopts
= "hvn+l+p:";
545 /* --- Parse the command line options --- */
547 bc__options(argc
, argv
, shortopts
, opts
);
549 /* --- Now do the job --- */
551 if (optind
== argc
&& isatty(0)) {
553 "%s: No filenames given, and standard input is a tty\n"
554 "To force reading from stdin, use `%s -'. For help, type "
556 optprog
, optprog
, optprog
);
560 #ifdef HAVE_LIBTERMCAP
561 if (bc__flags
& f_highlight
)
562 tgetent(bc__termcap
, getenv("TERM"));
569 /* --- If output is to a terminal, try paging --- *
571 * All programs which spew text should do this ;-)
576 bc__pager
= getenv("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");
586 signal(SIGPIPE
, bc__sigPipe
);
591 /* --- Now go through all the files --- */
594 bc__file(stdin
, out
);
596 while (optind
< argc
) {
597 if (strcmp(argv
[optind
], "-") == 0)
598 bc__file(stdin
, out
);
600 in
= fopen(argv
[optind
], "rb");
603 "%s: Couldn't open input file: %s\n",
604 optprog
, strerror(errno
));
612 if (bc__flags
& f_tty
)
619 /*----- That's all, folks -------------------------------------------------*/