1 /**********************************************************************************************************************************/
2 /* Module       : BAS2TAP.C                                                                                                       */
3 /* Executable   : BAS2TAP.EXE                                                                                                     */
4 /* Doc file     : BAS2TAP.DOC                                                                                                     */
5 /* Version type : Single file                                                                                                     */
6 /* Last changed : 20-01-2013  16:00                                                                                               */
7 /* Update count : 18                                                                                                              */
8 /* OS type      : Generic                                                                                                         */
9 /*                Watcom C = wcl386 -mf -fp3 -fpi -3r -oxnt -w4 -we bas2tap.c                                                     */
10 /*                MS C     = cl /Ox /G2 /AS bas2tap.c /F 1000                                                                     */
11 /*                gcc      = gcc -Wall -O2 bas2tap.c -o bas2tap -lm ; strip bas2tap                                               */
12 /*                SAS/C    = sc link math=ieee bas2tap.c                                                                          */
13 /* Libs needed  : math                                                                                                            */
14 /* Description  : Convert ASCII BASIC file to TAP tape image emulator file                                                        */
15 /*                                                                                                                                */
16 /* Notes        : There's a check for a define "__DEBUG__", which generates tons of output if defined.                            */
17 /*                                                                                                                                */
18 /*                Copyleft (C) 1998-2013 ThunderWare Research Center, written by Martijn van der Heide.                           */
19 /*                                                                                                                                */
20 /*                This program is free software; you can redistribute it and/or                                                   */
21 /*                modify it under the terms of the GNU General Public License                                                     */
22 /*                as published by the Free Software Foundation; either version 2                                                  */
23 /*                of the License, or (at your option) any later version.                                                          */
24 /*                                                                                                                                */
25 /*                This program is distributed in the hope that it will be useful,                                                 */
26 /*                but WITHOUT ANY WARRANTY; without even the implied warranty of                                                  */
27 /*                MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the                                                   */
28 /*                GNU General Public License for more details.                                                                    */
29 /*                                                                                                                                */
30 /*                You should have received a copy of the GNU General Public License                                               */
31 /*                along with this program; if not, write to the Free Software                                                     */
32 /*                Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA  02111-1307, USA.                                     */
33 /*                                                                                                                                */
34 /**********************************************************************************************************************************/
35 
36 #include <sys/types.h>
37 #include <stdio.h>
38 #include <stdlib.h>
39 #include <errno.h>
40 #include <string.h>
41 #include <ctype.h>
42 #include <math.h>
43 
44 /**********************************************************************************************************************************/
45 /* Some compilers don't define the following things, so I define them here...                                                     */
46 /**********************************************************************************************************************************/
47 
48 #ifdef __WATCOMC__
49 #define x_strnicmp(_S1,_S2,_Len)  strnicmp (_S1, _S2, _Len)
50 #define x_log2(_X)                log2 (_X)
51 #else
x_strnicmp(char * _S1,char * _S2,int _Len)52 int x_strnicmp (char *_S1, char *_S2, int _Len)                                        /* Case independant partial string compare */
53 {
54   for ( ; _Len && *_S1 && *_S2 && toupper (*_S1) == toupper (*_S2) ; _S1 ++, _S2 ++, _Len --)
55     ;
56   return (_Len ? (int)toupper (*_S1) - (int)toupper (*_S2) : 0);
57 }
58 #define x_log2(_X)                (log (_X) / log (2.0))                     /* If your compiler doesn't know the 'log2' function */
59 #endif
60 
61 //#define __DEBUG__
62 
63 typedef unsigned char  byte;
64 #ifndef FALSE
65 typedef unsigned char  bool;                                              /* If your compiler doesn't know this variable type yet */
66 #define TRUE           1
67 #define FALSE          0
68 #endif
69 
70 /**********************************************************************************************************************************/
71 /* Define the global variables                                                                                                    */
72 /**********************************************************************************************************************************/
73 
74 struct TokenMap_s
75 {
76   char *Token;
77   byte  TokenType;
78         /* Type   0 = No special meaning                                                   */
79         /* Type   1 = Always keyword                                                       */
80         /* Type   2 = Can be both keyword and non-keyword (colour parameters)              */
81         /* Type   3 = Numeric expression token                                             */
82         /* Type   4 = String expression token                                              */
83         /* Type   5 = May only appear in (L)PRINT statements (AT and TAB)                  */
84         /* Type   6 = Type-less (normal ASCII or expression token)                         */
85   byte  KeywordClass[8];                                     /* The class this keyword belongs to, as defined in the Spectrum ROM */
86         /* This table is used by expression tokens as well. Class 12 was added for this purpose */
87         /* Class  0 = No further operands                                                  */
88         /* Class  1 = Used in LET. A variable is required                                  */
89         /* Class  2 = Used in LET. An expression, numeric or string, must follow           */
90         /* Class  3 = A numeric expression may follow. Zero to be used in case of default  */
91         /* Class  4 = A single character variable must follow                              */
92         /* Class  5 = A set of items may be given                                          */
93         /* Class  6 = A numeric expression must follow                                     */
94         /* Class  7 = Handles colour items                                                 */
95         /* Class  8 = Two numeric expressions, separated by a comma, must follow           */
96         /* Class  9 = As for class 8 but colour items may precede the expression           */
97         /* Class 10 = A string expression must follow                                      */
98         /* Class 11 = Handles cassette routines                                            */
99         /* The following classes are not available in the ROM but were needed              */
100         /* Class 12 = One or more string expressions, separated by commas, must follow     */
101         /* Class 13 = One or more expressions, separated by commas, must follow            */
102         /* Class 14 = One or more variables, separated by commas, must follow (READ)       */
103         /* Class 15 = DEF FN                                                               */
104 } TokenMap[256] = {
105 
106    /* Everything below ASCII 32 */
107    {NULL, 6, { 0 }}, {NULL, 6, { 0 }}, {NULL, 6, { 0 }}, {NULL, 6, { 0 }}, {NULL, 6, { 0 }},
108    {NULL, 6, { 0 }},                                                                                                   /* Print ' */
109    {NULL, 6, { 0 }}, {NULL, 6, { 0 }}, {NULL, 6, { 0 }}, {NULL, 6, { 0 }}, {NULL, 6, { 0 }}, {NULL, 6, { 0 }}, {NULL, 6, { 0 }},
110    {"(eoln)", 6, { 0 }},                                                                                                    /* CR */
111    {NULL, 6, { 0 }},                                                                                                    /* Number */
112    {NULL, 6, { 0 }},
113    {NULL, 6, { 0 }},                                                                                                       /* INK */
114    {NULL, 6, { 0 }},                                                                                                     /* PAPER */
115    {NULL, 6, { 0 }},                                                                                                     /* FLASH */
116    {NULL, 6, { 0 }},                                                                                                    /* BRIGHT */
117    {NULL, 6, { 0 }},                                                                                                   /* INVERSE */
118    {NULL, 6, { 0 }},                                                                                                      /* OVER */
119    {NULL, 6, { 0 }},                                                                                                        /* AT */
120    {NULL, 6, { 0 }},                                                                                                       /* TAB */
121    {NULL, 6, { 0 }}, {NULL, 6, { 0 }}, {NULL, 6, { 0 }}, {NULL, 6, { 0 }}, {NULL, 6, { 0 }}, {NULL, 6, { 0 }}, {NULL, 6, { 0 }},
122    {NULL, 6, { 0 }},
123 
124    /* Normal ASCII set */
125    {"\x20", 6, { 0 }}, {"\x21", 6, { 0 }}, {"\x22", 6, { 0 }}, {"\x23", 6, { 0 }}, {"\x24", 6, { 0 }}, {"\x25", 6, { 0 }},
126    {"\x26", 6, { 0 }}, {"\x27", 6, { 0 }}, {"\x28", 6, { 0 }}, {"\x29", 6, { 0 }}, {"\x2A", 6, { 0 }}, {"\x2B", 6, { 0 }},
127    {"\x2C", 6, { 0 }}, {"\x2D", 6, { 0 }}, {"\x2E", 6, { 0 }}, {"\x2F", 6, { 0 }}, {"\x30", 6, { 0 }}, {"\x31", 6, { 0 }},
128    {"\x32", 6, { 0 }}, {"\x33", 6, { 0 }}, {"\x34", 6, { 0 }}, {"\x35", 6, { 0 }}, {"\x36", 6, { 0 }}, {"\x37", 6, { 0 }},
129    {"\x38", 6, { 0 }}, {"\x39", 6, { 0 }}, {"\x3A", 2, { 0 }}, {"\x3B", 6, { 0 }}, {"\x3C", 6, { 0 }}, {"\x3D", 6, { 0 }},
130    {"\x3E", 6, { 0 }}, {"\x3F", 6, { 0 }}, {"\x40", 6, { 0 }}, {"\x41", 6, { 0 }}, {"\x42", 6, { 0 }}, {"\x43", 6, { 0 }},
131    {"\x44", 6, { 0 }}, {"\x45", 6, { 0 }}, {"\x46", 6, { 0 }}, {"\x47", 6, { 0 }}, {"\x48", 6, { 0 }}, {"\x49", 6, { 0 }},
132    {"\x4A", 6, { 0 }}, {"\x4B", 6, { 0 }}, {"\x4C", 6, { 0 }}, {"\x4D", 6, { 0 }}, {"\x4E", 6, { 0 }}, {"\x4F", 6, { 0 }},
133    {"\x50", 6, { 0 }}, {"\x51", 6, { 0 }}, {"\x52", 6, { 0 }}, {"\x53", 6, { 0 }}, {"\x54", 6, { 0 }}, {"\x55", 6, { 0 }},
134    {"\x56", 6, { 0 }}, {"\x57", 6, { 0 }}, {"\x58", 6, { 0 }}, {"\x59", 6, { 0 }}, {"\x5A", 6, { 0 }}, {"\x5B", 6, { 0 }},
135    {"\x5C", 6, { 0 }}, {"\x5D", 6, { 0 }}, {"\x5E", 6, { 0 }}, {"\x5F", 6, { 0 }}, {"\x60", 6, { 0 }}, {"\x61", 6, { 0 }},
136    {"\x62", 6, { 0 }}, {"\x63", 6, { 0 }}, {"\x64", 6, { 0 }}, {"\x65", 6, { 0 }}, {"\x66", 6, { 0 }}, {"\x67", 6, { 0 }},
137    {"\x68", 6, { 0 }}, {"\x69", 6, { 0 }}, {"\x6A", 6, { 0 }}, {"\x6B", 6, { 0 }}, {"\x6C", 6, { 0 }}, {"\x6D", 6, { 0 }},
138    {"\x6E", 6, { 0 }}, {"\x6F", 6, { 0 }}, {"\x70", 6, { 0 }}, {"\x71", 6, { 0 }}, {"\x72", 6, { 0 }}, {"\x73", 6, { 0 }},
139    {"\x74", 6, { 0 }}, {"\x75", 6, { 0 }}, {"\x76", 6, { 0 }}, {"\x77", 6, { 0 }}, {"\x78", 6, { 0 }}, {"\x79", 6, { 0 }},
140    {"\x7A", 6, { 0 }}, {"\x7B", 6, { 0 }}, {"\x7C", 6, { 0 }}, {"\x7D", 6, { 0 }}, {"\x7E", 6, { 0 }}, {"\x7F", 6, { 0 }},
141 
142    /* Block graphics without shift */
143    {"\x80", 6, { 0 }}, {"\x81", 6, { 0 }}, {"\x82", 6, { 0 }}, {"\x83", 6, { 0 }}, {"\x84", 6, { 0 }}, {"\x85", 6, { 0 }},
144    {"\x86", 6, { 0 }}, {"\x87", 6, { 0 }},
145 
146    /* Block graphics with shift */
147    {"\x88", 6, { 0 }}, {"\x89", 6, { 0 }}, {"\x8A", 6, { 0 }}, {"\x8B", 6, { 0 }}, {"\x8C", 6, { 0 }}, {"\x8D", 6, { 0 }},
148    {"\x8E", 6, { 0 }}, {"\x8F", 6, { 0 }},
149 
150    /* UDGs */
151    {"\x90", 6, { 0 }}, {"\x91", 6, { 0 }}, {"\x92", 6, { 0 }}, {"\x93", 6, { 0 }}, {"\x94", 6, { 0 }}, {"\x95", 6, { 0 }},
152    {"\x96", 6, { 0 }}, {"\x97", 6, { 0 }}, {"\x98", 6, { 0 }}, {"\x99", 6, { 0 }}, {"\x9A", 6, { 0 }}, {"\x9B", 6, { 0 }},
153    {"\x9C", 6, { 0 }}, {"\x9D", 6, { 0 }}, {"\x9E", 6, { 0 }}, {"\x9F", 6, { 0 }}, {"\xA0", 6, { 0 }}, {"\xA1", 6, { 0 }},
154    {"\xA2", 6, { 0 }},
155 
156    {"SPECTRUM",  1, { 0 }},                                                                                   /* For Spectrum 128 */
157    {"PLAY",      1, { 12 }},
158 
159    /* BASIC tokens - expression */
160    {"RND",       3, { 0 }},
161    {"INKEY$",    4, { 0 }},
162    {"PI",        3, { 0 }},
163    {"FN",        3, { 1, '(', 13, ')', 0 }},
164    {"POINT",     3, { '(', 8, ')', 0 }},
165    {"SCREEN$",   4, { '(', 8, ')', 0 }},
166    {"ATTR",      3, { '(', 8, ')', 0 }},
167    {"AT",        5, { 8, 0 }},
168    {"TAB",       5, { 6, 0 }},
169    {"VAL$",      4, { 10, 0 }},
170    {"CODE",      3, { 10, 0 }},
171    {"VAL",       3, { 10, 0 }},
172    {"LEN",       3, { 10, 0 }},
173    {"SIN",       3, { 6, 0 }},
174    {"COS",       3, { 6, 0 }},
175    {"TAN",       3, { 6, 0 }},
176    {"ASN",       3, { 6, 0 }},
177    {"ACS",       3, { 6, 0 }},
178    {"ATN",       3, { 6, 0 }},
179    {"LN",        3, { 6, 0 }},
180    {"EXP",       3, { 6, 0 }},
181    {"INT",       3, { 6, 0 }},
182    {"SQR",       3, { 6, 0 }},
183    {"SGN",       3, { 6, 0 }},
184    {"ABS",       3, { 6, 0 }},
185    {"PEEK",      3, { 6, 0 }},
186    {"IN",        3, { 6, 0 }},
187    {"USR",       3, { 6, 0 }},
188    {"STR$",      4, { 6, 0 }},
189    {"CHR$",      4, { 6, 0 }},
190    {"NOT",       3, { 6, 0 }},
191    {"BIN",       6, { 0 }},
192    {"OR",        6, { 5, 0 }},   /*  -\                                                  */
193    {"AND",       6, { 5, 0 }},   /*   |                                                  */
194    {"<=",        6, { 5, 0 }},   /*   | These are handled directly within ScanExpression */
195    {">=",        6, { 5, 0 }},   /*   |                                                  */
196    {"<>",        6, { 5, 0 }},   /*  -/                                                  */
197    {"LINE",      6, { 0 }},
198    {"THEN",      6, { 0 }},
199    {"TO",        6, { 0 }},
200    {"STEP",      6, { 0 }},
201 
202    /* BASIC tokens - keywords */
203    {"DEF FN",    1, { 15, 0 }},                 /* Special treatment - insertion of call-by-value room required for the evaluator */
204    {"CAT",       1, { 11, 0 }},
205    {"FORMAT",    1, { 11, 0 }},
206    {"MOVE",      1, { 11, 0 }},
207    {"ERASE",     1, { 11, 0 }},
208    {"OPEN #",    1, { 11, 0 }},
209    {"CLOSE #",   1, { 11, 0 }},
210    {"MERGE",     1, { 11, 0 }},
211    {"VERIFY",    1, { 11, 0 }},
212    {"BEEP",      1, { 8, 0 }},
213    {"CIRCLE",    1, { 9, ',', 6, 0 }},
214    {"INK",       2, { 7, 0 }},
215    {"PAPER",     2, { 7, 0 }},
216    {"FLASH",     2, { 7, 0 }},
217    {"BRIGHT",    2, { 7, 0 }},
218    {"INVERSE",   2, { 7, 0 }},
219    {"OVER",      2, { 7, 0 }},
220    {"OUT",       1, { 8, 0 }},
221    {"LPRINT",    1, { 5, 0 }},
222    {"LLIST",     1, { 3, 0 }},
223    {"STOP",      1, { 0 }},
224    {"READ",      1, { 14, 0 }},
225    {"DATA",      2, { 13, 0 }},
226    {"RESTORE",   1, { 3, 0 }},
227    {"NEW",       1, { 0 }},
228    {"BORDER",    1, { 6, 0 }},
229    {"CONTINUE",  1, { 0 }},
230    {"DIM",       1, { 1, '(', 13, ')', 0 }},
231    {"REM",       1, { 5, 0 }},                                                                 /* (Special: taken out separately) */
232    {"FOR",       1, { 4, '=', 6, 0xCC, 6, 0xCD, 6, 0 }},                                /* (Special: STEP (0xCD) is not required) */
233    {"GO TO",     1, { 6, 0 }},
234    {"GO SUB",    1, { 6, 0 }},
235    {"INPUT",     1, { 5, 0 }},
236    {"LOAD",      1, { 11, 0 }},
237    {"LIST",      1, { 3, 0 }},
238    {"LET",       1, { 1, '=', 2, 0 }},
239    {"PAUSE",     1, { 6, 0 }},
240    {"NEXT",      1, { 4, 0 }},
241    {"POKE",      1, { 8, 0 }},
242    {"PRINT",     1, { 5, 0 }},
243    {"PLOT",      1, { 9, 0 }},
244    {"RUN",       1, { 3, 0 }},
245    {"SAVE",      1, { 11, 0 }},
246    {"RANDOMIZE", 1, { 3, 0 }},
247    {"IF",        1, { 6, 0xCB, 0 }},
248    {"CLS",       1, { 0 }},
249    {"DRAW",      1, { 9, ',', 6, 0 }},
250    {"CLEAR",     1, { 3, 0 }},
251    {"RETURN",    1, { 0 }},
252    {"COPY",      1, { 0 }}};
253 
254 #define MAXLINELENGTH 1024
255 
256 char ConvertedSpectrumLine[MAXLINELENGTH + 1];
257 byte ResultingLine[MAXLINELENGTH + 1];
258 
259 struct TapeHeader_s
260 {
261   byte LenLo1;
262   byte LenHi1;
263   byte Flag1;
264   byte HType;
265   char HName[10];
266   byte HLenLo;
267   byte HLenHi;
268   byte HStartLo;
269   byte HStartHi;
270   byte HBasLenLo;
271   byte HBasLenHi;
272   byte Parity1;
273   byte LenLo2;
274   byte LenHi2;
275   byte Flag2;
276 } TapeHeader = {19, 0,                                                            /* Len header */
277                 0,                                                                /* Flag header */
278                 0, {32, 32, 32, 32, 32, 32, 32, 32, 32, 32}, 0, 0, 0, 128, 0, 0,  /* The header itself */
279                 0,                                                                /* Parity header */
280                 0, 0,                                                             /* Len converted BASIC */
281                 255};                                                             /* Flag converted BASIC */
282 
283 int   Is48KProgram    = -1;                                                                                       /* -1 = unknown */
284                                                                                                                   /*  1 = 48K     */
285                                                                                                                   /*  0 = 128K    */
286 int   UsesInterface1  = -1;                                                           /* -1 = unknown                             */
287                                                                                       /*  0 = either Interface1 or Opus Discovery */
288                                                                                       /*  1 = Interface1                          */
289                                                                                       /*  2 = Opus Discovery                      */
290 bool  CaseIndependant = FALSE;
291 bool  Quiet           = FALSE;                                                 /* Suppress banner and progress indication if TRUE */
292 bool  NoWarnings      = FALSE;                                                                       /* Suppress warnings if TRUE */
293 bool  DoCheckSyntax   = TRUE;
294 bool  TokenBracket    = FALSE;
295 bool  HandlingDEFFN   = FALSE;                                                                         /* Exceptional instruction */
296 bool  InsideDEFFN     = FALSE;
297 #define DEFFN           0xCE
298 FILE *ErrStream;
299 
300 /**********************************************************************************************************************************/
301 /* Let's be lazy and define a very commonly used error message....                                                                */
302 /**********************************************************************************************************************************/
303 
304 #define BADTOKEN(_Exp,_Got)        fprintf (ErrStream, "ERROR in line %d, statement %d - Expected %s, but got \"%s\"\n", \
305                                             BasicLineNo, StatementNo, _Exp, _Got)
306 
307 /**********************************************************************************************************************************/
308 /* And let's generate tons of debugging info too....                                                                              */
309 /**********************************************************************************************************************************/
310 
311 #ifdef __DEBUG__
312   char ListSpaces[20];
313   int  RecurseLevel;
314 #endif
315 
316 /**********************************************************************************************************************************/
317 /* Prototype all functions                                                                                                        */
318 /**********************************************************************************************************************************/
319 
320 int  GetLineNumber     (char **FirstAfter);
321 int  MatchToken        (int BasicLineNo, bool WantKeyword, char **LineIndex, byte *Token);
322 int  HandleNumbers     (int BasicLineNo, char **BasicLine, byte **SpectrumLine);
323 int  HandleBIN         (int BasicLineNo, char **BasicLine, byte **SpectrumLine);
324 int  ExpandSequences   (int BasicLineNo, char **BasicLine, byte **SpectrumLine, bool StripSpaces);
325 int  PrepareLine       (char *LineIn, int FileLineNo, char **FirstToken);
326 bool ScanVariable      (int BasicLineNo, int StatementNo, int Keyword, byte **Index, bool *Type, int *NameLen, int AllowSlicing);
327 bool SliceDirectString (int BasicLineNo, int StatementNo, int Keyword, byte **Index);
328 bool ScanStream        (int BasicLineNo, int StatementNo, int Keyword, byte **Index);
329 bool ScanChannel       (int BasicLineNo, int StatementNo, int Keyword, byte **Index, byte *WhichChannel);
330 bool SignalInterface1  (int BasicLineNo, int StatementNo, int NewMode);
331 bool CheckEnd          (int BasicLineNo, int StatementNo, byte **Index);
332 bool ScanExpression    (int BasicLineNo, int StatementNo, int Keyword, byte **Index, bool *Type, int Level);
333 bool HandleClass01     (int BasicLineNo, int StatementNo, int Keyword, byte **Index, bool *Type);
334 bool HandleClass02     (int BasicLineNo, int StatementNo, int Keyword, byte **Index, bool Type);
335 bool HandleClass03     (int BasicLineNo, int StatementNo, int Keyword, byte **Index);
336 bool HandleClass04     (int BasicLineNo, int StatementNo, int Keyword, byte **Index);
337 bool HandleClass05     (int BasicLineNo, int StatementNo, int Keyword, byte **Index);
338 bool HandleClass06     (int BasicLineNo, int StatementNo, int Keyword, byte **Index);
339 bool HandleClass07     (int BasicLineNo, int StatementNo, int Keyword, byte **Index);
340 bool HandleClass08     (int BasicLineNo, int StatementNo, int Keyword, byte **Index);
341 bool HandleClass09     (int BasicLineNo, int StatementNo, int Keyword, byte **Index);
342 bool HandleClass10     (int BasicLineNo, int StatementNo, int Keyword, byte **Index);
343 bool HandleClass11     (int BasicLineNo, int StatementNo, int Keyword, byte **Index);
344 bool HandleClass12     (int BasicLineNo, int StatementNo, int Keyword, byte **Index);
345 bool HandleClass13     (int BasicLineNo, int StatementNo, int Keyword, byte **Index);
346 bool HandleClass14     (int BasicLineNo, int StatementNo, int Keyword, byte **Index);
347 bool HandleClass15     (int BasicLineNo, int StatementNo, int Keyword, byte **Index);
348 bool CheckSyntax       (int BasicLineNo, byte *Line);
349 
350 /**********************************************************************************************************************************/
351 /* Start of the program                                                                                                           */
352 /**********************************************************************************************************************************/
353 
GetLineNumber(char ** FirstAfter)354 int GetLineNumber (char **FirstAfter)
355 
356 /**********************************************************************************************************************************/
357 /* Pre   : The line must have been prepared into (global) `ConvertedSpectrumLine'.                                                */
358 /* Post  : The BASIC line number has been returned, or -1 if there was none.                                                      */
359 /* Import: None.                                                                                                                  */
360 /**********************************************************************************************************************************/
361 
362 {
363   int   LineNo     = 0;
364   char *LineIndex;
365   bool  SkipSpaces = TRUE;
366   bool  Continue   = TRUE;
367 
368   LineIndex = ConvertedSpectrumLine;
369   while (*LineIndex && Continue)
370     if (*LineIndex == ' ')                                                                                 /* Skip leading spaces */
371     {
372       if (SkipSpaces)
373         LineIndex ++;
374       else
375         Continue = FALSE;
376     }
377     else if (isdigit (*LineIndex))                                                                              /* Process number */
378     {
379       LineNo = LineNo * 10 + *(LineIndex ++) - '0';
380       SkipSpaces = FALSE;
381     }
382     else
383       Continue = FALSE;
384   *FirstAfter = LineIndex;
385   if (SkipSpaces)                                                                                          /* Nothing found yet ? */
386     return (-1);
387   else
388     while ((**FirstAfter) == ' ')                                                                         /* Skip trailing spaces */
389       (*FirstAfter) ++;
390   return (LineNo);
391 }
392 
MatchToken(int BasicLineNo,bool WantKeyword,char ** LineIndex,byte * Token)393 int MatchToken (int BasicLineNo, bool WantKeyword, char **LineIndex, byte *Token)
394 
395 /**********************************************************************************************************************************/
396 /* Pre   : `WantKeyword' is TRUE if we need in keyword match, `LineIndex' holds the position to match.                            */
397 /* Post  : If there was a match, the token value is returned in `Token' and `LineIndex' is pointing after the string plus any     */
398 /*         any trailing space.                                                                                                    */
399 /*         The return value is 0 for no match, -2 for an error, -1 for a match of the wrong type, 1 for a good match.             */
400 /* Import: None.                                                                                                                  */
401 /**********************************************************************************************************************************/
402 
403 {
404   int    Cnt;
405   size_t Length;
406   size_t LongestMatch = 0;
407   bool   Match        = FALSE;
408   bool   Match2;
409 
410   if ((**LineIndex) == ':')                                                                                  /* Special exception */
411   {
412     LongestMatch = 1;
413     Match = TRUE;
414     *Token = ':';
415   }
416   else for (Cnt = 0xA3 ; Cnt <= 0xFF ; Cnt ++)                                                 /* (Keywords start after the UDGs) */
417   {
418     Length = strlen (TokenMap[Cnt].Token);
419     if (CaseIndependant)
420       Match2 = !x_strnicmp (*LineIndex, TokenMap[Cnt].Token, Length);
421     else
422       Match2 = !strncmp (*LineIndex, TokenMap[Cnt].Token, Length);
423     if (Match2)
424       if (Length > LongestMatch)
425       {
426         LongestMatch = Length;
427         Match = TRUE;
428         *Token = Cnt;
429       }
430   }
431   if (!Match)
432     return (0);                                                                                               /* Signal: no match */
433   if (isalpha (*(*LineIndex + LongestMatch - 1)) && isalpha (*(*LineIndex + LongestMatch)))         /* Continueing alpha string ? */
434     return (0);                                            /* Then there's no match after all! (eg. 'INT' must not match 'INTER') */
435   *LineIndex += LongestMatch;                                                                                /* Go past the token */
436   while ((**LineIndex) == ' ')                                                                            /* Skip trailing spaces */
437     (*LineIndex) ++;
438   if (*Token == 0xA3 || *Token == 0xA4)                                                                 /* 'SPECTRUM' or 'PLAY' ? */
439     switch (Is48KProgram)                                                                        /* Then the program must be 128K */
440     {
441       case -1 : Is48KProgram = 0; break;                                                                          /* Set the flag */
442       case  1 : fprintf (ErrStream, "ERROR - Line %d contains a 128K keyword, but the program\n"
443                          "also uses UDGs \'T\' and/or \'U\'\n", BasicLineNo);
444                 return (-2);
445       case  0 : break;
446     }
447   if ((WantKeyword && TokenMap[*Token].TokenType == 0) ||                                /* Wanted keyword but got something else */
448       (!WantKeyword && TokenMap[*Token].TokenType == 1))                        /* Did not want a keyword but got one nonetheless */
449     return (-1);                                                                              /* Signal: match, but of wrong type */
450   else
451     return (1);                                                                                                 /* Signal: match! */
452 }
453 
HandleNumbers(int BasicLineNo,char ** BasicLine,byte ** SpectrumLine)454 int HandleNumbers (int BasicLineNo, char **BasicLine, byte **SpectrumLine)
455 
456 /**********************************************************************************************************************************/
457 /* Pre   : `BasicLineNo' holds the current BASIC line number, `BasicLine' points into the line, `SpectrumLine' points to the      */
458 /*         TAPped Spectrum line.                                                                                                  */
459 /* Post  : If there was a (floating point) number at this position, it has been processed into `SpectrumLine' and `LineIndex' is  */
460 /*         pointing after the number.                                                                                             */
461 /*         The return value is: 0 = no number, 1 = number done, -1 = number error (already reported).                             */
462 /* Import: None.                                                                                                                  */
463 /**********************************************************************************************************************************/
464 
465 {
466 #define SHIFT31BITS   (double)2147483648.0                                                                            /* (= 2^31) */
467   char   *StartOfNumber;
468   double  Value   = 0.0;
469   double  Divider = 1.0;
470   double  Exp     = 0.0;
471   int     IntValue;
472   byte    Sign    = 0x00;
473   unsigned long Mantissa;
474 
475   if (!isdigit (**BasicLine) &&                                                             /* Current character is not a digit ? */
476       (**BasicLine) != '.')                                                               /* And not a decimal point (eg. '.5') ? */
477     return (0);                                                                                 /* Then it can hardly be a number */
478   StartOfNumber = *BasicLine;
479   while (isdigit (**BasicLine))                                                                    /* First read the integer part */
480     Value = Value * 10 + *((*BasicLine) ++) - '0';
481   if ((**BasicLine) == '.')                                                                                    /* Decimal point ? */
482   {                                                                                                      /* Read the decimal part */
483     (*BasicLine) ++;
484     while (isdigit (**BasicLine))
485       Value = Value + (Divider /= 10) * (*((*BasicLine) ++) - '0');
486   }
487   if ((**BasicLine) == 'e' || (**BasicLine) == 'E')                                                                 /* Exponent ? */
488   {
489     (*BasicLine) ++;
490     if ((**BasicLine) == '+')                                                            /* Both "Ex" and "E+x" do the same thing */
491       (*BasicLine) ++;
492     else if ((**BasicLine) == '-')                                                                           /* Negative exponent */
493     {
494       Sign = 0xFF;
495       (*BasicLine) ++;
496     }
497     while (isdigit (**BasicLine))                                                                      /* Read the exponent value */
498       Exp = Exp * 10 + *((*BasicLine) ++) - '0';
499     if (Sign == 0x00)                                                           /* Raise the resulting value to the read exponent */
500       Value = Value * pow (10.0, Exp);
501     else
502       Value = Value / pow (10.0, Exp);
503   }
504   strncpy ((char *)*SpectrumLine, StartOfNumber, *BasicLine - StartOfNumber);                     /* Insert the ASCII value first */
505   (*SpectrumLine) += (*BasicLine - StartOfNumber);
506   IntValue = (int)floor (Value);
507   if (Value == IntValue && Value >= -65536 && Value < 65536)                                                   /* Small integer ? */
508   {
509     *((*SpectrumLine) ++) = 0x0E;                                                                         /* Insert number marker */
510     *((*SpectrumLine) ++) = 0x00;
511     if (IntValue >= 0)                                                                                             /* Insert sign */
512       *((*SpectrumLine) ++) = 0x00;
513     else
514     {
515       *((*SpectrumLine) ++) = 0xFF;
516       IntValue += 65536;                                          /* Maintain bug in Spectrum ROM - INT(-65536) will result in -1 */
517     }
518     *((*SpectrumLine) ++) = (byte)(IntValue & 0xFF);
519     *((*SpectrumLine) ++) = (byte)(IntValue >> 8);
520     *((*SpectrumLine) ++) = 0x00;
521   }
522   else                                                                             /* Need to store in full floating point format */
523   {
524     if (Value < 0)
525     {
526       Sign = 0x80;                                                                              /* Sign bit is high bit of byte 2 */
527       Value = -Value;
528     }
529     else
530       Sign = 0x00;
531     Exp = floor (x_log2 (Value));
532     if (Exp < -129 || Exp > 126)
533     {
534       fprintf (ErrStream, "ERROR - Number too big in line %d\n", BasicLineNo);
535       return (-1);
536     }
537     Mantissa = (unsigned long)floor ((Value / pow (2.0, Exp) - 1.0) * SHIFT31BITS + 0.5);                   /* Calculate mantissa */
538     *((*SpectrumLine) ++) = 0x0E;                                                                         /* Insert number marker */
539     *((*SpectrumLine) ++) = (byte)Exp + 0x81;                                                                  /* Insert exponent */
540     *((*SpectrumLine) ++) = (byte)((Mantissa >> 24) & 0x7F) | Sign;                                            /* Insert mantissa */
541     *((*SpectrumLine) ++) = (byte)((Mantissa >> 16) & 0xFF);                                                     /* (Big endian!) */
542     *((*SpectrumLine) ++) = (byte)((Mantissa >> 8) & 0xFF);
543     *((*SpectrumLine) ++) = (byte)(Mantissa & 0xFF);
544   }
545   return (1);
546 }
547 
HandleBIN(int BasicLineNo,char ** BasicLine,byte ** SpectrumLine)548 int HandleBIN (int BasicLineNo, char **BasicLine, byte **SpectrumLine)
549 
550 /**********************************************************************************************************************************/
551 /* Pre   : `BasicLineNo' holds the current BASIC line number, `BasicLine' points into the line just past the BIN token,           */
552 /*         `SpectrumLine' points to the TAPped Spectrum line.                                                                     */
553 /* Post  : If there was a BINary number at this position, it has been processed into `SpectrumLine' and `LineIndex' is pointing   */
554 /*         after the number.                                                                                                      */
555 /*         The return value is: 1 = number done, -1 = number error (already reported).                                            */
556 /* Import: None.                                                                                                                  */
557 /**********************************************************************************************************************************/
558 
559 {
560   int Value = 0;
561 
562   while ((**BasicLine) == '0' || (**BasicLine) == '1')                                                 /* Read only binary digits */
563   {
564     Value = Value * 2 + **BasicLine - '0';
565     if (Value > 65535)
566     {
567       fprintf (ErrStream, "ERROR - Number too big in line %d\n", BasicLineNo);
568       return (-1);
569     }
570     *((*SpectrumLine) ++) = *((*BasicLine) ++);                                                            /* (Copy digit across) */
571   }
572   *((*SpectrumLine) ++) = 0x0E;                                                                           /* Insert number marker */
573   *((*SpectrumLine) ++) = 0x00;                                                                /* (A small integer by definition) */
574   *((*SpectrumLine) ++) = 0x00;
575   *((*SpectrumLine) ++) = (byte)(Value & 0xFF);
576   *((*SpectrumLine) ++) = (byte)(Value >> 8);
577   *((*SpectrumLine) ++) = 0x00;
578   return (1);
579 }
580 
ExpandSequences(int BasicLineNo,char ** BasicLine,byte ** SpectrumLine,bool StripSpaces)581 int ExpandSequences (int BasicLineNo, char **BasicLine, byte **SpectrumLine, bool StripSpaces)
582 
583 /**********************************************************************************************************************************/
584 /* Pre   : `BasicLineNo' holds the current BASIC line number, `BasicLine' points into the line, `SpectrumLine' points to the      */
585 /*         TAPped Spectrum line.                                                                                                  */
586 /* Post  : If there was an expandable '{...}' sequence at this position, it has been processed into `SpectrumLine', `LineIndex'   */
587 /*         is pointing after the sequence. Returned is -1 for error, 0 for no expansion, 1 for expansion.                         */
588 /* Import: None.                                                                                                                  */
589 /**********************************************************************************************************************************/
590 
591 {
592   char *StartOfSequence;
593   byte  Attribute       = 0;
594   byte  AttributeLength = 0;
595   byte  AttributeVal1   = 0;
596   byte  AttributeVal2   = 0;
597   byte  OldCharacter;
598   int   Cnt;
599 
600   if (**BasicLine != '{')
601     return (0);
602   StartOfSequence = (*BasicLine) + 1;
603   /* 'CODE' and 'CAT' were added for the sole purpuse of allowing them to be OPEN #'ed as channels! */
604   if (!x_strnicmp (StartOfSequence, "CODE}", 5))                                                               /* Special: 'CODE' */
605   {
606     *((*SpectrumLine) ++) = 0xAF;
607     (*BasicLine) += 6;
608     return (1);
609   }
610   if (!x_strnicmp (StartOfSequence, "CAT}", 4))                                                                 /* Special: 'CAT' */
611   {
612     *((*SpectrumLine) ++) = 0xCF;
613     (*BasicLine) += 5;
614     return (1);
615   }
616   if (!x_strnicmp (StartOfSequence, "(C)}", 4))
617   {                                                                                             /* Form "{(C)}" -> copyright sign */
618     *((*SpectrumLine) ++) = 0x7F;
619     (*BasicLine) += 5;
620     if (StripSpaces)
621       while ((**BasicLine) == ' ')                                                                        /* Skip trailing spaces */
622         (*BasicLine) ++;
623     return (1);
624   }
625   if (*StartOfSequence == '+' && *(StartOfSequence + 1) >= '1' && *(StartOfSequence + 1) <= '8' && *(StartOfSequence + 2) == '}')
626   {                                                                                   /* Form "{+X}" -> block graphics with shift */
627     *((*SpectrumLine) ++) = 0x88 + (((*(StartOfSequence + 1) - '0') % 8) ^ 7);
628     (*BasicLine) += 4;
629     if (StripSpaces)
630       while ((**BasicLine) == ' ')
631         (*BasicLine) ++;
632     return (1);
633   }
634   if (*StartOfSequence == '-' && *(StartOfSequence + 1) >= '1' && *(StartOfSequence + 1) <= '8' && *(StartOfSequence + 2) == '}')
635   {                                                                                /* Form "{-X}" -> block graphics without shift */
636     *((*SpectrumLine) ++) = 0x80 + (*(StartOfSequence + 1) - '0') % 8;
637     (*BasicLine) += 4;
638     if (StripSpaces)
639       while ((**BasicLine) == ' ')
640         (*BasicLine) ++;
641     return (1);
642   }
643   if (toupper (*StartOfSequence) >= 'A' && toupper (*StartOfSequence) <= 'U' && *(StartOfSequence + 1) == '}')
644   {                                                                                                          /* Form "{X}" -> UDG */
645     if (toupper (*StartOfSequence) == 'T' || toupper (*StartOfSequence) == 'U')                                   /* 'T' or 'U' ? */
646       switch (Is48KProgram)                                                                       /* Then the program must be 48K */
647       {
648         case -1 : Is48KProgram = 1; break;                                                                        /* Set the flag */
649         case  0 : fprintf (ErrStream, "ERROR - Line %d contains UDGs \'T\' and/or \'U\'\n"
650                            "but the program was already marked 128K\n", BasicLineNo);
651                   return (-1);
652         case  1 : break;
653       }
654     *((*SpectrumLine) ++) = 0x90 + toupper (*StartOfSequence) - 'A';
655     (*BasicLine) += 3;
656     if (StripSpaces)
657       while ((**BasicLine) == ' ')
658         (*BasicLine) ++;
659     return (1);
660   }
661   if (isxdigit (*StartOfSequence) && isxdigit (*(StartOfSequence + 1)) && *(StartOfSequence + 2) == '}')
662   {                                                                                                    /* Form "{XX}" -> below 32 */
663     if (*StartOfSequence <= '9')
664       (**SpectrumLine) = *StartOfSequence - '0';
665     else
666       (**SpectrumLine) = toupper (*StartOfSequence) - 'A' + 10;
667     if (*(StartOfSequence + 1) <= '9')
668       (**SpectrumLine) = (**SpectrumLine) * 16 + *(StartOfSequence + 1) - '0';
669     else
670       (**SpectrumLine) = (**SpectrumLine) * 16 + toupper (*(StartOfSequence + 1)) - 'A' + 10;
671     (*SpectrumLine) ++;
672     (*BasicLine) += 4;
673     if (StripSpaces)
674       while ((**BasicLine) == ' ')
675         (*BasicLine) ++;
676     return (1);
677   }
678   if (!x_strnicmp (StartOfSequence, "INK", 3))
679   {
680     Attribute = 0x10;
681     AttributeLength = 3;
682   }
683   else if (!x_strnicmp (StartOfSequence, "PAPER", 5))
684   {
685     Attribute = 0x11;
686     AttributeLength = 5;
687   }
688   else if (!x_strnicmp (StartOfSequence, "FLASH", 5))
689   {
690     Attribute = 0x12;
691     AttributeLength = 5;
692   }
693   else if (!x_strnicmp (StartOfSequence, "BRIGHT", 6))
694   {
695     Attribute = 0x13;
696     AttributeLength = 6;
697   }
698   else if (!x_strnicmp (StartOfSequence, "INVERSE", 7))
699   {
700     Attribute = 0x14;
701     AttributeLength = 7;
702   }
703   else if (!x_strnicmp (StartOfSequence, "OVER", 4))
704   {
705     Attribute = 0x15;
706     AttributeLength = 4;
707   }
708   else if (!x_strnicmp (StartOfSequence, "AT", 2))
709   {
710     Attribute = 0x16;
711     AttributeLength = 2;
712   }
713   else if (!x_strnicmp (StartOfSequence, "TAB", 3))
714   {
715     Attribute = 0x17;
716     AttributeLength = 3;
717   }
718   if (Attribute > 0)
719   {
720     StartOfSequence += AttributeLength;
721     while (*StartOfSequence == ' ')
722       StartOfSequence ++;
723     while (isdigit (*StartOfSequence))
724       AttributeVal1 = AttributeVal1 * 10 + *(StartOfSequence ++) - '0';
725     if (Attribute == 0x16 || Attribute == 0x17)
726     {
727       if (*StartOfSequence != ',')
728         Attribute = 0;
729       else
730       {
731         StartOfSequence ++;                                                                              /* (Step past the comma) */
732         while (*StartOfSequence == ' ')
733           StartOfSequence ++;
734         while (isdigit (*StartOfSequence))
735           AttributeVal2 = AttributeVal2 * 10 + *(StartOfSequence ++) - '0';
736       }
737     }
738     if (*StartOfSequence != '}')                                                                          /* Need closing bracket */
739       Attribute = 0;
740     if (Attribute > 0)
741     {
742       *((*SpectrumLine) ++) = Attribute;
743       *((*SpectrumLine) ++) = AttributeVal1;
744       if (Attribute == 0x16 || Attribute == 0x17)
745         *((*SpectrumLine) ++) = AttributeVal2;
746       (*BasicLine) = StartOfSequence + 1;
747       if (StripSpaces)
748         while ((**BasicLine) == ' ')
749           (*BasicLine) ++;
750       return (1);
751     }
752   }
753   if (!NoWarnings)
754   {
755     for (Cnt = 0 ; *((*BasicLine) + Cnt) && *((*BasicLine) + Cnt) != '}' ; Cnt ++)
756       ;
757     if (*((*BasicLine) + Cnt) == '}')
758     {
759       OldCharacter = *((*BasicLine) + Cnt + 1);
760       *((*BasicLine) + Cnt + 1) = '\0';
761       printf ("WARNING - Unexpandable sequence \"%s\" in line %d\n", (*BasicLine), BasicLineNo);
762       *((*BasicLine) + Cnt + 1) = OldCharacter;
763       return (0);
764     }
765   }
766   return (0);
767 }
768 
PrepareLine(char * LineIn,int FileLineNo,char ** FirstToken)769 int PrepareLine (char *LineIn, int FileLineNo, char **FirstToken)
770 
771 /**********************************************************************************************************************************/
772 /* Pre   : `LineIn' points to the read line, `FileLineNo' holds the real line number.                                             */
773 /* Post  : Multiple spaces have been removed (unless within a string), the BASIC line number has been found and `FirstToken' is   */
774 /*         pointing at the first non-whitespace character after the line number.                                                  */
775 /*         Bad characters are reported, as well as any other error. The return value is the BASIC line number, -1 if error, or    */
776 /*         -2 if the (empty!) line should be skipped.                                                                             */
777 /* Import: GetLineNumber.                                                                                                         */
778 /**********************************************************************************************************************************/
779 
780 {
781   char  *IndexIn;
782   char  *IndexOut;
783   bool   InString        = FALSE;
784   bool   SingleSeparator = FALSE;
785   bool   StillOk         = TRUE;
786   bool   DoingREM        = FALSE;
787   int    BasicLineNo     = -1;
788   static int PreviousBasicLineNo = -1;
789 
790   IndexIn = LineIn;
791   IndexOut = ConvertedSpectrumLine;
792   while (*IndexIn && StillOk)
793   {
794     if (*IndexIn == '\t')                                                                                   /* EXCEPTION: Print ' */
795     {
796       *(IndexOut ++) = 0x06;
797       IndexIn ++;
798     }
799     else if (*IndexIn < 32 || *IndexIn >= 127)                                                /* (Exclude copyright sign as well) */
800       StillOk = FALSE;
801     else
802     {
803       if (!DoingREM)
804         if (!x_strnicmp (IndexIn, " REM ", 5) ||                                                 /* Going through REM statement ? */
805             !x_strnicmp (IndexIn, ":REM ", 5))
806           DoingREM = TRUE;                                                          /* Signal: copy anything and everything ASCII */
807       if (InString || DoingREM)
808         *(IndexOut ++) = *IndexIn;
809       else
810       {
811         if (*IndexIn == ' ')
812         {
813           if (!SingleSeparator)                                                                         /* Remove multiple spaces */
814           {
815             SingleSeparator = TRUE;
816             *(IndexOut ++) = *IndexIn;
817           }
818         }
819         else
820         {
821           SingleSeparator = FALSE;
822           *(IndexOut ++) = *IndexIn;
823         }
824       }
825       if (*IndexIn == '\"' && !DoingREM)
826         InString = !InString;
827       IndexIn ++;
828     }
829   }
830   *IndexOut = '\0';
831   if (!StillOk)
832     if (*IndexIn == 0x0D || *IndexIn == 0x0A)                                                        /* 'Correct' for end-of-line */
833       StillOk = TRUE;                                                                     /* (Accept CR and/or LF as end-of-line) */
834   BasicLineNo = GetLineNumber (FirstToken);
835   if (InString)
836     fprintf (ErrStream, "ERROR - %s line %d misses terminating quote\n",
837              BasicLineNo < 0 ? "ASCII" : "BASIC", BasicLineNo < 0 ? FileLineNo : BasicLineNo);
838   else if (!StillOk)
839     fprintf (ErrStream, "ERROR - %s line %d contains a bad character (code %02Xh)\n",
840              BasicLineNo < 0 ? "ASCII" : "BASIC", BasicLineNo < 0 ? FileLineNo : BasicLineNo, *IndexIn);
841   else if (BasicLineNo < 0)                                                                         /* Could not read line number */
842   {
843     if (!(**FirstToken))                                                                            /* Line is completely empty ? */
844     {
845       if (!NoWarnings)
846         printf ("WARNING - Skipping empty ASCII line %d\n", FileLineNo);
847       return (-2);                                                                                    /* Signal: skip entire line */
848     }
849     else
850     {
851       fprintf (ErrStream, "ERROR - Missing line number in ASCII line %d\n", FileLineNo);
852       StillOk = FALSE;
853     }
854   }
855   else if (PreviousBasicLineNo >= 0)                                                                      /* Not the first line ? */
856   {
857     if (BasicLineNo < PreviousBasicLineNo)                                            /* This line number smaller than previous ? */
858     {
859       fprintf (ErrStream, "ERROR - Line number %d is smaller than previous line number %d\n", BasicLineNo, PreviousBasicLineNo);
860       StillOk = FALSE;
861     }
862     else if (BasicLineNo == PreviousBasicLineNo && !NoWarnings)                                 /* Same line number as previous ? */
863       printf ("WARNING - Duplicate use of line number %d\n", BasicLineNo);                  /* (BASIC can handle it after all...) */
864   }
865   else if (!(**FirstToken))                                                                 /* Line contains only a line number ? */
866   {
867     fprintf (ErrStream, "ERROR - Line %d contains no statements!\n", BasicLineNo);
868     StillOk = FALSE;
869   }
870   PreviousBasicLineNo = BasicLineNo;                                                                 /* Remember this line number */
871   if (!InString && StillOk)
872     return (BasicLineNo);
873   else
874     return (-1);
875 }
876 
CheckEnd(int BasicLineNo,int StatementNo,byte ** Index)877 bool CheckEnd (int BasicLineNo, int StatementNo, byte **Index)
878 
879 /**********************************************************************************************************************************/
880 /* Pre   : `BasicLineNo' holds the line number, `StatementNo' the statement number, `Index' the current position in the line.     */
881 /* Post  : A check is made whether the end of the current statement has been reached.                                             */
882 /*         If so, an error is reported and TRUE is returned (so FALSE indicates that everything is still fine and dandy).         */
883 /* Import: none.                                                                                                                  */
884 /**********************************************************************************************************************************/
885 
886 {
887   if (**Index == ':' || **Index == 0x0D)                                                     /* End of statement or end of line ? */
888   {
889     fprintf (ErrStream, "ERROR in line %d, statement %d - Unexpected end of statement\n", BasicLineNo, StatementNo);
890     return (TRUE);
891   }
892   return (FALSE);
893 }
894 
ScanVariable(int BasicLineNo,int StatementNo,int Keyword,byte ** Index,bool * Type,int * NameLen,int AllowSlicing)895 bool ScanVariable (int BasicLineNo, int StatementNo, int Keyword, byte **Index, bool *Type, int *NameLen, int AllowSlicing)
896 
897 /**********************************************************************************************************************************/
898 /* Pre   : `BasicLineNo' holds the line number, `StatementNo' the statement number, `Keyword' the keyword to which this operand   */
899 /*         belongs, `Index' the current position in the line.                                                                     */
900 /*         `AllocSlicing' is one of the following values:                                                                         */
901 /*         -1 = Don't check for slicing/indexing (used by DEF FN)                                                                 */
902 /*         0 = No slicing/indexing allowed                                                                                        */
903 /*         1 = Either slicing or indexing may follow (indices being numeric)                                                      */
904 /*         2 = Only numeric indexing may follow (used by LET and READ)                                                            */
905 /* Post  : A check has been made whether there's a variable at the current position. If so, it has been skipped.                  */
906 /*         Slicing is handled here as well, but notice that this is not necessarily correct!                                      */
907 /*         Single letter string variables can be either flat or array and both possibilities are considered here.                 */
908 /*         Both "a$(1 TO 10)" and "a$(1, 2)" are correct to BASIC, but depend on whether a "DIM" statement was used.              */
909 /*         The length of the found string (without any '$') is returned in `NameLen', its type is returned in `Type' (TRUE for    */
910 /*         numeric and FALSE for string variables). The return value is TRUE is all went well. Errors have already been reported. */
911 /*         The return value is FALSE either when no variable is at this point or an error was found.                              */
912 /*         `NameLen' is returned 0 if no variable was detected here, or > 0 if in error.                                          */
913 /* Import: ScanExpression.                                                                                                        */
914 /**********************************************************************************************************************************/
915 
916 {
917   bool SubType;
918   bool IsArray = FALSE;
919   bool SetTokenBracket = FALSE;
920 
921   Keyword = Keyword;                                                                                    /* (Keep compilers happy) */
922   *Type = TRUE;                                                                                      /* Assume it will be numeric */
923   *NameLen = 0;
924   if (!isalpha (**Index))                                                /* The first character must be alphabetic for a variable */
925     return (FALSE);
926   *NameLen = 1;
927   while (isalnum (*(++ (*Index))))                                                              /* Read on, until end of the word */
928     (*NameLen) ++;
929   if (**Index == '$')                                                                                 /* It's a string variable ? */
930   {
931     if (*NameLen > 1)                                                   /* String variables can only have a single character name */
932     {
933       fprintf (ErrStream, "ERROR in line %d, statement %d - String variables can only have single character names\n",
934                BasicLineNo, StatementNo);
935       return (FALSE);
936     }
937     (*Index) ++;
938     *Type = FALSE;
939   }
940 #ifdef __DEBUG__
941   printf ("DEBUG - %sScanVariable, Type is %s\n", ListSpaces, *Type ? "NUM" : "ALPHA");
942 #endif
943   if (AllowSlicing >= 0 && **Index == '(')                                                                  /* Slice the string ? */
944   {
945 #ifdef __DEBUG__
946     printf ("DEBUG - %sScanVariable, reading index\n", ListSpaces);
947 #endif
948     if (*NameLen > 1)                                                             /* Arrays can only have a single character name */
949     {
950       fprintf (ErrStream, "ERROR in line %d, statement %d - Arrays can only have single character names\n",
951                BasicLineNo, StatementNo);
952       return (FALSE);
953     }
954     if (AllowSlicing == 0)                                                                      /* Slicing/Indexing not allowed ? */
955     {
956       fprintf (ErrStream, "ERROR in line %d, statement %d - Slicing/Indexing not allowed\n", BasicLineNo, StatementNo);
957       return (FALSE);
958     }
959     (*Index) ++;                                                                                            /* (Skip the bracket) */
960     if (**Index == ')')                                                                           /* Empty slice "a$()" is not ok */
961     {
962       fprintf (ErrStream, "ERROR in line %d, statement %d - Empty array index not allowed\n", BasicLineNo, StatementNo);
963       return (FALSE);
964     }
965     if (**Index == 0xCC)                                                                           /* "a$( TO num)" or "a$( TO )" */
966     {
967       if (AllowSlicing == 2)
968       {
969         fprintf (ErrStream, "ERROR in line %d, statement %d - Slicing token \"TO\" inappropriate for arrays\n",
970                  BasicLineNo, StatementNo);
971         return (FALSE);
972       }
973     }
974     else                                                                                      /* Not "a$( TO num)" nor "a$( TO )" */
975     {
976       if (!TokenBracket)
977       {
978         TokenBracket = TRUE;                                                                          /* Allow complex expression */
979         SetTokenBracket = TRUE;
980       }
981       if (!ScanExpression (BasicLineNo, StatementNo, '(', Index, &SubType, 0))                                 /* First parameter */
982         return (FALSE);
983       if (SetTokenBracket)
984         TokenBracket = FALSE;
985       if (!SubType)                                                                                            /* Must be numeric */
986       {
987         fprintf (ErrStream, "ERROR in line %d, statement %d - Variables indices must be numeric\n", BasicLineNo, StatementNo);
988         return (FALSE);
989       }
990       if (**Index == ')')                                                                                      /* "a$(num)" is ok */
991       {
992         (*Index) ++;
993 #ifdef __DEBUG__
994         printf ("DEBUG - %sScanVariable, index ending, next char is \"%s\"\n", ListSpaces, TokenMap[**Index].Token);
995 #endif
996         return (TRUE);
997       }
998     }
999     if (**Index != 0xCC && **Index != ',')                                                          /* Either an array or a slice */
1000     {
1001       fprintf (ErrStream, "ERROR in line %d, statement %d - Unexpected index character \"%c\"\n",
1002                BasicLineNo, StatementNo, **Index);
1003       return (FALSE);
1004     }
1005     if (**Index == ',')
1006       IsArray = TRUE;
1007     else
1008     {
1009       if (AllowSlicing == 2)
1010       {
1011         fprintf (ErrStream, "ERROR in line %d, statement %d - Slicing token \"TO\" inappropriate for arrays\n",
1012                  BasicLineNo, StatementNo);
1013         return (FALSE);
1014       }
1015       if (*Type)                                                                          /* Only character strings can be sliced */
1016       {
1017         fprintf (ErrStream, "ERROR in line %d, statement %d - Only character strings can be sliced\n", BasicLineNo, StatementNo);
1018         return (FALSE);
1019       }
1020     }
1021     do
1022     {
1023       (*Index) ++;                                                                  /* Skip each "," (or the "TO" for non-arrays) */
1024       if (!TokenBracket)
1025       {
1026         TokenBracket = TRUE;
1027         SetTokenBracket = TRUE;
1028       }
1029       if (!ScanExpression (BasicLineNo, StatementNo, '(', Index, &SubType, 0))                     /* Second or further parameter */
1030         return (FALSE);
1031       if (SetTokenBracket)
1032         TokenBracket = FALSE;
1033       if (!SubType)                                                                                            /* Must be numeric */
1034       {
1035         fprintf (ErrStream, "ERROR in line %d, statement %d - Variables indices must be numeric\n", BasicLineNo, StatementNo);
1036         return (FALSE);
1037       }
1038       if (!IsArray && **Index != ')')
1039       {
1040         BADTOKEN ("\")\"", TokenMap[**Index].Token);
1041         return (FALSE);
1042       }
1043       else if (IsArray && **Index != ',' && **Index != ')' && **Index != 0xCC)
1044       {
1045         BADTOKEN ("\",\"", TokenMap[**Index].Token);
1046         return (FALSE);
1047       }
1048     }
1049     while (**Index != ')');
1050     (*Index) ++;                                                                                   /* (Step past closing bracket) */
1051 #ifdef __DEBUG__
1052     printf ("DEBUG - %sScanVariable, index ending, next char is \"%s\"\n", ListSpaces, TokenMap[**Index].Token);
1053 #endif
1054   }
1055   return (TRUE);
1056 }
1057 
SliceDirectString(int BasicLineNo,int StatementNo,int Keyword,byte ** Index)1058 bool SliceDirectString (int BasicLineNo, int StatementNo, int Keyword, byte **Index)
1059 
1060 /**********************************************************************************************************************************/
1061 /* Pre   : `BasicLineNo' holds the line number, `StatementNo' the statement number, `Keyword' the keyword to which this operand   */
1062 /*         belongs, `Index' the current position in the line.                                                                     */
1063 /*         A direct string has just been read and a '(' character is currently under the cursor.                                  */
1064 /* Post  : Slicing is handled here.                                                                                               */
1065 /*         Possible are "string"(), "string"(num), "string"( TO ), "string"(num TO ), "string"( TO num) and "string"(num TO num). */
1066 /*         The return value is FALSE if an error was found (which has already been reported here).                                */
1067 /* Import: ScanExpression.                                                                                                        */
1068 /**********************************************************************************************************************************/
1069 
1070 {
1071   bool SubType;
1072   bool SetTokenBracket = FALSE;
1073 
1074   Keyword = Keyword;                                                                                    /* (Keep compilers happy) */
1075   (*Index) ++;                                                                                   /* Step past the opening bracket */
1076   if (**Index == ')')                                                                                /* Empty slice "abc"() is ok */
1077   {
1078     (*Index) ++;
1079     return (TRUE);
1080   }
1081   if (**Index != 0xCC)                                                                      /* Not "abc"( TO num) nor "abc"( TO ) */
1082   {
1083     if (!TokenBracket)
1084     {
1085       TokenBracket = TRUE;
1086       SetTokenBracket = TRUE;
1087     }
1088     if (!ScanExpression (BasicLineNo, StatementNo, '(', Index, &SubType, 0))                                   /* First parameter */
1089       return (FALSE);
1090     if (SetTokenBracket)
1091       TokenBracket = FALSE;
1092     if (!SubType)                                                                                              /* Must be numeric */
1093     {
1094       fprintf (ErrStream, "ERROR in line %d, statement %d - Slice values must be numeric\n", BasicLineNo, StatementNo);
1095       return (FALSE);
1096     }
1097   }
1098   if (**Index == ')')                                                                                         /* "abc"(num) is ok */
1099   {
1100     (*Index) ++;
1101     return (TRUE);
1102   }
1103   if (**Index != 0xCC)                                                                                                  /* ('TO') */
1104   {
1105     fprintf (ErrStream, "ERROR in line %d, statement %d - Unexpected index character\n", BasicLineNo, StatementNo);
1106     return (FALSE);
1107   }
1108   (*Index) ++;
1109   if (**Index == ')')                                                                                     /* "abc"(num TO ) is ok */
1110   {
1111     (*Index) ++;
1112     return (TRUE);
1113   }
1114   if (!TokenBracket)
1115   {
1116     TokenBracket = TRUE;
1117     SetTokenBracket = TRUE;
1118   }
1119   if (!ScanExpression (BasicLineNo, StatementNo, '(', Index, &SubType, 0))                                    /* Second parameter */
1120     return (FALSE);
1121   if (SetTokenBracket)
1122     TokenBracket = FALSE;
1123   if (!SubType)                                                                                                /* Must be numeric */
1124   {
1125     fprintf (ErrStream, "ERROR in line %d, statement %d - Slice values must be numeric\n", BasicLineNo, StatementNo);
1126     return (FALSE);
1127   }
1128   if (**Index != ')')
1129   {
1130     BADTOKEN ("\")\"", TokenMap[**Index].Token);
1131     return (FALSE);
1132   }
1133   (*Index) ++;                                                                                     /* (Step past closing bracket) */
1134   return (TRUE);
1135 }
1136 
ScanStream(int BasicLineNo,int StatementNo,int Keyword,byte ** Index)1137 bool ScanStream (int BasicLineNo, int StatementNo, int Keyword, byte **Index)
1138 
1139 /**********************************************************************************************************************************/
1140 /* Pre   : `BasicLineNo' holds the line number, `StatementNo' the statement number, `Keyword' the keyword to which this operand   */
1141 /*         belongs, `Index' the current position in the line.                                                                     */
1142 /*         A stream hash mark (`#') has just been read.                                                                           */
1143 /* Post  : The following stream number is checked to be a numeric expression.                                                     */
1144 /*         The return value is FALSE if an error was found (which has already been reported here).                                */
1145 /* Import: HandleClass06.                                                                                                         */
1146 /**********************************************************************************************************************************/
1147 
1148 {
1149   if (!SignalInterface1 (BasicLineNo, StatementNo, 0))
1150     return (FALSE);
1151   return (HandleClass06 (BasicLineNo, StatementNo, Keyword, Index));                                   /* Find numeric expression */
1152 }
1153 
SignalInterface1(int BasicLineNo,int StatementNo,int NewMode)1154 bool SignalInterface1 (int BasicLineNo, int StatementNo, int NewMode)
1155 
1156 /**********************************************************************************************************************************/
1157 /* Pre   : `BasicLineNo' holds the line number, `StatementNo' the statement number, `NewMode' holds the required hardware mode.   */
1158 /* Post  : The required hardware is tested for conflicts.                                                                         */
1159 /*         The return value is FALSE if there was a conflict (which has already been reported here).                              */
1160 /* Import: none.                                                                                                                  */
1161 /**********************************************************************************************************************************/
1162 
1163 {
1164   if ((NewMode == 1 && UsesInterface1 == 2) ||                                 /* Interface1 required, but already flagged Opus ? */
1165       (NewMode == 2 && UsesInterface1 == 1))                                   /* Opus required, but already flagged Interface1 ? */
1166   {
1167     fprintf (ErrStream, "ERROR in line %d, statement %d - The program uses commands that are specific\n"
1168                         "for Interface 1 and Opus Discovery, but don't exist on both devices\n",
1169              BasicLineNo, StatementNo);
1170     return (FALSE);
1171   }
1172   UsesInterface1 = NewMode;
1173   return (TRUE);
1174 }
1175 
ScanChannel(int BasicLineNo,int StatementNo,int Keyword,byte ** Index,byte * WhichChannel)1176 bool ScanChannel (int BasicLineNo, int StatementNo, int Keyword, byte **Index, byte *WhichChannel)
1177 
1178 /**********************************************************************************************************************************/
1179 /* Pre   : `BasicLineNo' holds the line number, `StatementNo' the statement number, `Keyword' the keyword to which this operand   */
1180 /*         belongs, `Index' the current position in the line.                                                                     */
1181 /* Post  : A channel identifier of the form   "x";n;   must follow. `x' is a single alphanumeric character, `n' is a numeric      */
1182 /*         expression, the rest are required characters.                                                                          */
1183 /*         The found channel identifier ('x') is returned (in lowercase) in `WhichChannel'.                                       */
1184 /*         The return value is FALSE if an error was found (which has already been reported here).                                */
1185 /* Import: HandleClass06, CheckEnd.                                                                                               */
1186 /**********************************************************************************************************************************/
1187 
1188 {
1189   int  NeededHardware = 0;                                                                            /* (Default to Interface 1) */
1190 
1191   *WhichChannel = '\0';
1192   if (CheckEnd (BasicLineNo, StatementNo, Index))
1193     return (FALSE);
1194   if (**Index != '\"')
1195   {
1196     if (!HandleClass06 (BasicLineNo, StatementNo, Keyword, Index))/* EXCEPTION: The Opus allows '<num>' to abbreviate '"m";<num>' */
1197     {
1198       fprintf (ErrStream, "Expected to find a channel identifier\n");
1199       return (FALSE);
1200     }
1201     *WhichChannel = 'm';
1202     if (!SignalInterface1 (BasicLineNo, StatementNo, 2))                                          /* Signal the Opus specificness */
1203       return (FALSE);
1204     if (CheckEnd (BasicLineNo, StatementNo, Index))
1205       return (FALSE);
1206     if (**Index != ';')
1207     {
1208       BADTOKEN ("\";\"", TokenMap[**Index].Token);
1209       return (FALSE);
1210     }
1211     (*Index) ++;
1212     if (CheckEnd (BasicLineNo, StatementNo, Index))
1213       return (FALSE);
1214   }
1215   else
1216   {
1217     (*Index) ++;
1218     if (CheckEnd (BasicLineNo, StatementNo, Index))
1219       return (FALSE);
1220     if (!isalpha (**Index) &&                                                                               /* (Ordinary channel) */
1221         **Index != '#' &&                                                                        /* (Linked channel, OPEN # only) */
1222         **Index != 0xAF &&                                                                       /* ('CODE' channel, OPEN # only) */
1223         **Index != 0xCF)                                                                          /* ('CAT' channel, OPEN # only) */
1224     {
1225       fprintf (ErrStream, "ERROR in line %d, statement %d - Channel name must be alphanumeric\n", BasicLineNo, StatementNo);
1226       return (FALSE);
1227     }
1228     *WhichChannel = tolower (**Index);
1229     (*Index) ++;
1230     if (CheckEnd (BasicLineNo, StatementNo, Index))
1231       return (FALSE);
1232     if (**Index != '\"')
1233     {
1234       fprintf (ErrStream, "ERROR in line %d, statement %d - Channel name must be single character\n", BasicLineNo, StatementNo);
1235       return (FALSE);
1236     }
1237     (*Index) ++;
1238     if (*WhichChannel == 'k' || *WhichChannel == 's' || *WhichChannel == 'p' ||                     /* (Normal Spectrum channels) */
1239         *WhichChannel == 'm' || *WhichChannel == 't' || *WhichChannel == 'b' ||
1240         *WhichChannel == '#' || *WhichChannel == 0xCF)                                                         /* ('CAT' channel) */
1241       NeededHardware = 0;
1242     else if (*WhichChannel == 'n')                                 /* Network channel is available on Interface 1 but not on Opus */
1243       NeededHardware = 1;
1244     else if (*WhichChannel == 'j' ||                                                                  /* (Opus: Joystick channel) */
1245              *WhichChannel == 'd' ||                                                                      /* (Opus: disk channel) */
1246              *WhichChannel == 0xAF)                                                                     /* (Opus: 'CODE' channel) */
1247       NeededHardware = 2;
1248     if (!SignalInterface1 (BasicLineNo, StatementNo, NeededHardware))
1249       return (FALSE);
1250     if (*WhichChannel == 'm' || *WhichChannel == 'd' || *WhichChannel == 'n' ||     /* Continue checking with these channels only */
1251         *WhichChannel == '#' || *WhichChannel == 0xCF)
1252     {
1253       if (CheckEnd (BasicLineNo, StatementNo, Index))
1254         return (FALSE);
1255       if (**Index != ';')
1256       {
1257         BADTOKEN ("\";\"", TokenMap[**Index].Token);
1258         return (FALSE);
1259       }
1260       (*Index) ++;
1261       if (!HandleClass06 (BasicLineNo, StatementNo, Keyword, Index))                                   /* Find numeric expression */
1262         return (FALSE);
1263       if (*WhichChannel == 'm')                                        /* Omly the 'm' channel requires a ';' character following */
1264       {
1265         if (CheckEnd (BasicLineNo, StatementNo, Index))
1266           return (FALSE);
1267         if (**Index != ';')
1268         {
1269           BADTOKEN ("\";\"", TokenMap[**Index].Token);
1270           return (FALSE);
1271         }
1272         (*Index) ++;
1273         if (CheckEnd (BasicLineNo, StatementNo, Index))
1274           return (FALSE);
1275       }
1276     }
1277   }
1278   return (TRUE);
1279 }
1280 
ScanExpression(int BasicLineNo,int StatementNo,int Keyword,byte ** Index,bool * Type,int Level)1281 bool ScanExpression (int BasicLineNo, int StatementNo, int Keyword, byte **Index, bool *Type, int Level)
1282 
1283 /**********************************************************************************************************************************/
1284 /* Pre   : `BasicLineNo' holds the line number, `StatementNo' the statement number, `Keyword' the keyword to which this operand   */
1285 /*         belongs, `Index' the current position in the line.                                                                     */
1286 /*         `Level' is used for recursion and must be 0 when called, unless when called from ScanVariable (then it must be 1).     */
1287 /* Post  : An expression must be found, either numerical or string. Its type is returned in `Type' (TRUE for numerical).          */
1288 /*         All subexpressions, between brackets, are dealt with using recursion.                                                  */
1289 /*         The return value is FALSE if an error was found (which has already been reported here).                                */
1290 /* Import: ScanExpression (recursive), SliceDirectString, ScanVariable, HandleClassXX.                                            */
1291 /**********************************************************************************************************************************/
1292 
1293 {
1294   bool More       = TRUE;
1295   bool SubType    = TRUE;                                                                          /* (Assume numeric expression) */
1296   bool SubSubType;
1297   bool TypeKnown  = FALSE;
1298   bool TotalTypeKnown = FALSE;
1299   bool Dummy;
1300   int  VarNameLen;
1301   int  ClassIndex = -1;
1302   byte ThisToken;
1303 
1304 #ifdef __DEBUG__
1305   RecurseLevel ++;
1306   memset (ListSpaces, ' ', RecurseLevel * 2);
1307   ListSpaces[RecurseLevel * 2] = '\0';
1308   printf ("DEBUG - %sEnter ScanExpression\n", ListSpaces);
1309 #endif
1310   if (**Index == '+' || **Index == '-')                                                                   /* Unary plus and minus */
1311   {
1312     *Type = TRUE;                                                                          /* Then we expect a numeric expression */
1313     TypeKnown = TRUE;
1314     (*Index) ++;                                                                                                 /* Skip the sign */
1315   }
1316   while (More)
1317   {
1318 #ifdef __DEBUG__
1319     printf ("DEBUG - %sScanExpression sub (keyword \"%s\"), first char is \"%s\"\n",
1320             ListSpaces, TokenMap[Keyword].Token, TokenMap[**Index].Token);
1321 #endif
1322     if (**Index == '(')                                                                                      /* Opening bracket ? */
1323     {
1324 #ifdef __DEBUG__
1325       printf ("DEBUG - %sRecurse ScanExpression for \"(\"\n", ListSpaces);
1326 #endif
1327       (*Index) ++;                                                                 /* The 'parent' steps past the opening bracket */
1328       if (!ScanExpression (BasicLineNo, StatementNo, '(', Index, &SubSubType, Level + 1))                              /* Recurse */
1329         return (FALSE);
1330       if (TypeKnown && SubSubType != SubType)                                                         /* Bad subexpression type ? */
1331       {
1332         fprintf (ErrStream, "ERROR in line %d, statement %d - Type conflict in expression\n", BasicLineNo, StatementNo);
1333         return (FALSE);
1334       }
1335       else if (!TypeKnown)                                                               /* We didn't have an expected type yet ? */
1336       {
1337         SubType = SubSubType;
1338         TypeKnown = TRUE;
1339       }
1340       (*Index) ++;                                                             /* The 'parent' steps past the closing bracket too */
1341       if (**Index == '(')                                                                                            /* Slicing ? */
1342       {
1343         if (!SubSubType)                                                                                 /* Result was a string ? */
1344         {
1345           if (!SliceDirectString (BasicLineNo, StatementNo, Keyword, Index))
1346             return (FALSE);
1347         }
1348         else                                                                       /* No, it was numerical, which you can't slice */
1349         {
1350           fprintf (ErrStream, "ERROR in line %d, statement %d - cannot slice a numerical value\n",
1351                    BasicLineNo, StatementNo);
1352           return (FALSE);
1353         }
1354       }
1355     }
1356     else if (**Index == ')')                                                                                 /* Closing bracket ? */
1357     {                                                  /* Leave the bracket for the parent, to allow functions (eg. "ATTR (...)") */
1358       if (!TotalTypeKnown)                                                                               /* 'Simple' expression ? */
1359         *Type = SubType;                                                                                       /* Set return type */
1360 #ifdef __DEBUG__
1361       printf ("DEBUG - %sLeave ScanExpression, Type is %s next char is \"%s\"\n",
1362               ListSpaces, *Type ? "NUM" : "ALPHA", TokenMap[**Index].Token);
1363       if (-- RecurseLevel > 0)
1364         memset (ListSpaces, ' ', RecurseLevel * 2);
1365       ListSpaces[RecurseLevel * 2] = '\0';
1366 #endif
1367       return (TRUE);                                                                                 /* Step out of the recursion */
1368     }
1369     else if (**Index == ':' || **Index == 0x0D)                                              /* End of statement or end of line ? */
1370     {
1371       if (!TotalTypeKnown)                                                                               /* 'Simple' expression ? */
1372         *Type = SubType;                                                                                       /* Set return type */
1373       if (Level)                                                                                         /* Not on lowest level ? */
1374       {
1375         fprintf (ErrStream, "ERROR in line %d, statement %d - too few closing brackets\n", BasicLineNo, StatementNo);
1376         return (FALSE);
1377       }
1378       More = FALSE;
1379     }
1380     else if (isdigit (**Index) || **Index == '.' || **Index == 0xC4)                                                  /* Number ? */
1381     {
1382       if (!TypeKnown)                                                                            /* Unknown expression type yet ? */
1383       {
1384         TypeKnown = TRUE;                                                                                /* Signal: it is numeric */
1385         SubType = TRUE;
1386       }
1387       else if (!SubType)                                                                         /* Type was known to be string ? */
1388       {
1389         fprintf (ErrStream, "ERROR in line %d, statement %d - Type conflict in expression\n", BasicLineNo, StatementNo);
1390         return (FALSE);
1391       }
1392       while (*(++ (*Index)) != 0x0E)                                                              /* Skip until the number marker */
1393         ;
1394       (*Index) ++;
1395     }
1396     else if (**Index == '\"')                                                                                  /* Direct string ? */
1397     {
1398       if (!TypeKnown)                                                                            /* Unknown expression type yet ? */
1399       {
1400         TypeKnown = TRUE;                                                                               /* Signal: it is a string */
1401         SubType = FALSE;
1402       }
1403       else if (SubType)                                                                         /* Type was known to be numeric ? */
1404       {
1405         fprintf (ErrStream, "ERROR in line %d, statement %d - Type conflict in expression\n", BasicLineNo, StatementNo);
1406         return (FALSE);
1407       }
1408       while (**Index == '\"')                         /* Concatenated strings are ok, since they allow the use of the " character */
1409       {
1410         while (*(++ (*Index)) != '\"')                                                                      /* Find closing quote */
1411           ;
1412         (*Index) ++;                                                                                              /* Step past it */
1413       }
1414       if (**Index == '(')                                                                                   /* String is sliced ? */
1415         if (!SliceDirectString (BasicLineNo, StatementNo, Keyword, Index))
1416           return (FALSE);
1417     }
1418     else if (ScanVariable (BasicLineNo, StatementNo, Keyword, Index, &SubSubType, &VarNameLen, 1))          /* Is it a variable ? */
1419     {
1420       if (!TypeKnown)                                                                            /* Unknown expression type yet ? */
1421       {
1422         TypeKnown = TRUE;                                                                                 /* Signal: it is string */
1423         SubType = SubSubType;
1424       }
1425       else if (SubType != SubSubType)                                                                /* Different type variable ? */
1426       {
1427         fprintf (ErrStream, "ERROR in line %d, statement %d - Type conflict in expression\n", BasicLineNo, StatementNo);
1428         return (FALSE);
1429       }
1430     }
1431     else if (VarNameLen != 0)                                                                                 /* (Not a variable) */
1432       return (FALSE);                                                                 /* (But an error that was already reported) */
1433     /* It's none of the above. Go check tokens */
1434     else switch (TokenMap[**Index].TokenType)
1435     {
1436       case 0 : fprintf (ErrStream, "ERROR in line %d, statement %d - Unexpected token \"%s\"\n",
1437                         BasicLineNo, StatementNo, TokenMap[**Index].Token);
1438                return (FALSE);
1439       case 1 :
1440       case 2 : fprintf (ErrStream, "ERROR in line %d, statement %d - Unexpected keyword \"%s\"\n",
1441                         BasicLineNo, StatementNo, TokenMap[**Index].Token);
1442                return (FALSE);
1443       case 3 :
1444       case 4 :
1445       case 5 : ThisToken = *((*Index) ++);
1446                if (TokenMap[ThisToken].TokenType == 5)
1447                {
1448                  if (Keyword != 0xF5 && Keyword != 0xE0)                                      /* Not handling a PRINT or LPRINT ? */
1449                  {
1450                    fprintf (ErrStream, "ERROR in line %d, statement %d - Unexpected token \"%s\"\n",
1451                             BasicLineNo, StatementNo, TokenMap[**Index].Token);
1452                    return (FALSE);
1453                  }
1454                }
1455                else if (ThisToken == 0xC0 && **Index == '\"')                                                 /* Special: USR "x" */
1456                {
1457                  (*Index) ++;                                                                    /* (Step past the opening quote) */
1458                  if (CheckEnd (BasicLineNo, StatementNo, Index))
1459                    return (FALSE);
1460                  if (toupper (**Index) < 'A' || toupper (**Index) > 'U')                                   /* Bad UDG character ? */
1461                  {
1462                    fprintf (ErrStream, "ERROR in line %d, statement %d - Bad UDG \"%s\"\n",
1463                             BasicLineNo, StatementNo, TokenMap[**Index].Token);
1464                    return (FALSE);
1465                  }
1466                  (*Index) ++;
1467                  if (CheckEnd (BasicLineNo, StatementNo, Index))
1468                    return (FALSE);
1469                  if (**Index != '\"')                                                                   /* More than one letter ? */
1470                  {
1471                    fprintf (ErrStream, "ERROR in line %d, statement %d - An UDG name may be only 1 letter\n",
1472                             BasicLineNo, StatementNo);
1473                    return (FALSE);
1474                  }
1475                  (*Index) --;
1476                  if (toupper (**Index) == 'T' || toupper (**Index) == 'U')                        /* One of the UDGs 'T' or 'U' ? */
1477                    switch (Is48KProgram)                                                          /* Then the program must be 48K */
1478                    {
1479                      case -1 : Is48KProgram = 1; break;                                                           /* Set the flag */
1480                      case  0 : fprintf (ErrStream, "ERROR - Line %d contains UDGs \'T\' and/or \'U\'\n"
1481                                         "but the program was already marked 128K\n", BasicLineNo);
1482                                return (FALSE);
1483                      case  1 : break;
1484                    }
1485                  (*Index) += 2;                                                       /* Step past the UDG name and closing quote */
1486                  break;                                                                                         /* Done, step out */
1487                }
1488                else
1489                {
1490                  if (!TypeKnown)                                                                 /* Unknown expression type yet ? */
1491                  {
1492                    TypeKnown = TRUE;                                                                         /* Set expected type */
1493                    SubType = (TokenMap[ThisToken].TokenType == 3);
1494                  }
1495                  else if ((SubType && TokenMap[ThisToken].TokenType == 4) ||
1496                           (!SubType && TokenMap[ThisToken].TokenType == 3))
1497                  {
1498                    fprintf (ErrStream, "ERROR in line %d, statement %d - Type conflict in expression\n", BasicLineNo, StatementNo);
1499                    return (FALSE);
1500                  }
1501                }
1502                ClassIndex = -1;
1503                while (TokenMap[ThisToken].KeywordClass[++ ClassIndex])                             /* Handle all class parameters */
1504                {
1505                  if (CheckEnd (BasicLineNo, StatementNo, Index))
1506                    return (FALSE);
1507                  else if (TokenMap[ThisToken].KeywordClass[ClassIndex] >= 32)                        /* Required token or class ? */
1508                  {
1509                    if (**Index != TokenMap[ThisToken].KeywordClass[ClassIndex])                               /* (Required token) */
1510                    {                                                                                         /* (Token not there) */
1511                      fprintf (ErrStream, "ERROR in line %d, statement %d - Expected \"%c\", but got \"%s\"\n",
1512                               BasicLineNo, StatementNo, TokenMap[ThisToken].KeywordClass[ClassIndex], TokenMap[**Index].Token);
1513                      return (FALSE);
1514                    }
1515                    else
1516                    {
1517                      if (**Index == '(')
1518                      {
1519 #ifdef __DEBUG__
1520                        printf ("DEBUG - %sTurning on token bracket\n", ListSpaces);
1521 #endif
1522                        TokenBracket = TRUE;
1523                      }
1524                      else if (**Index == ')')
1525                      {
1526 #ifdef __DEBUG__
1527                        printf ("DEBUG - %sTurning off token bracket\n", ListSpaces);
1528 #endif
1529                        TokenBracket = FALSE;
1530                      }
1531                      (*Index) ++;
1532                    }
1533                  }
1534                  else                                                                                          /* (Command class) */
1535                  {
1536                    switch (TokenMap[ThisToken].KeywordClass[ClassIndex])
1537                    {
1538                      case  1 : if (!HandleClass01 (BasicLineNo, StatementNo, ThisToken, Index, &Dummy))          /* (Special: FN) */
1539                                  return (FALSE);
1540                                break;
1541                      case  3 : if (!HandleClass03 (BasicLineNo, StatementNo, ThisToken, Index))
1542                                  return (FALSE);
1543                                break;
1544                      case  5 : if (!HandleClass05 (BasicLineNo, StatementNo, ThisToken, Index))
1545                                  return (FALSE);
1546                                break;
1547                      case  6 : if (!HandleClass06 (BasicLineNo, StatementNo, ThisToken, Index))
1548                                  return (FALSE);
1549                                break;
1550                      case  8 : if (!HandleClass08 (BasicLineNo, StatementNo, ThisToken, Index))
1551                                  return (FALSE);
1552                                break;
1553                      case 10 : if (!HandleClass10 (BasicLineNo, StatementNo, ThisToken, Index))
1554                                  return (FALSE);
1555                                break;
1556                      case 12 : if (!HandleClass12 (BasicLineNo, StatementNo, ThisToken, Index))
1557                                  return (FALSE);
1558                                break;
1559                      case 13 : if (!HandleClass13 (BasicLineNo, StatementNo, ThisToken, Index))
1560                                  return (FALSE);
1561                                break;
1562                      case 14 : if (!HandleClass14 (BasicLineNo, StatementNo, ThisToken, Index))
1563                                  return (FALSE);
1564                                break;
1565                    }
1566 #ifdef __DEBUG__
1567                    printf ("DEBUG - %sScanExpression status, Type is %s, next char is \"%s\"\n",
1568                            ListSpaces, *Type ? "NUM" : "ALPHA", TokenMap[**Index].Token);
1569 #endif
1570                  }
1571                }
1572                if (ThisToken == 0xA6)                                                                                 /* INKEY$ ? */
1573                  if (**Index == '#')                                                                  /* Type 'INKEY$#<stream>' ? */
1574                  {
1575                    (*Index) ++;
1576                    if (!ScanStream (BasicLineNo, StatementNo, ThisToken, Index))
1577                      return (FALSE);
1578                  }
1579                break;
1580     }
1581     /* Piece done, continue */
1582     if (TokenMap[Keyword].TokenType == 3 || TokenMap[Keyword].TokenType == 4)              /* Just did an operand to a function ? */
1583     {                                                                                    /* Then step back to evaluate the result */
1584       if (!TotalTypeKnown)
1585         *Type = SubType;
1586 #ifdef __DEBUG__
1587       printf ("DEBUG - %sLeave ScanExpression, Type is %s, next char is \"%s\"\n",
1588               ListSpaces, *Type ? "NUM" : "ALPHA", TokenMap[**Index].Token);
1589       if (-- RecurseLevel > 0)
1590         memset (ListSpaces, ' ', RecurseLevel * 2);
1591       ListSpaces[RecurseLevel * 2] = '\0';
1592 #endif
1593       return (TRUE);
1594     }
1595     if (More)
1596     {
1597       if (**Index == 0xC5 || **Index == 0xC6)                                                                 /* ('OR' and 'AND') */
1598       {
1599 #ifdef __DEBUG__
1600         printf ("DEBUG - %sRecurse ScanExpression for \"%s\"\n", ListSpaces, TokenMap[**Index].Token);
1601 #endif
1602         //if (!TotalTypeKnown)                                                           /* 'Simple' expression before the AND/OR ? */
1603           //*Type = SubType;
1604         if (**Index == 0xC5 && !*Type)
1605         {
1606           fprintf (ErrStream, "ERROR in line %d, statement %d - \"OR\" requires a numeric left value\n", BasicLineNo, StatementNo);
1607           return (FALSE);
1608         }
1609         ThisToken = *((*Index) ++);                                                   /* Step over the operator - but remember it */
1610         if (!ScanExpression (BasicLineNo, StatementNo, ThisToken, Index, &SubSubType, 0))                /* Recurse - at level 0! */
1611           return (FALSE);
1612         if (!SubSubType)                                       /* The expression at the right must be numeric for both AND and OR */
1613         {
1614           fprintf (ErrStream, "ERROR in line %d, statement %d - \"%s\" requires a numeric right value\n",
1615                    BasicLineNo, StatementNo, TokenMap[ThisToken].Token);
1616           return (FALSE);
1617         }
1618         if (!TypeKnown)                                                                  /* We didn't have an expected type yet ? */
1619         {
1620           TypeKnown = TRUE;
1621           TotalTypeKnown = TRUE;
1622           SubType = *Type = (bool)(ThisToken == 0xC6 && !*Type ? FALSE : TRUE);                          /* Signal resulting type */
1623                                                                                                   /* x$ AND y -> result is string */
1624                                                                                                   /* x AND y -> result is numeric */
1625                                                                                                    /* x OR y -> result is numeric */
1626         }
1627         More = FALSE;              /* (Because the recursing causes the expression to be evaluated right to left, we're done now) */
1628       }
1629       else if ((**Index == '=' || **Index == '<' || **Index == '>' ||     /* EXCEPTION: equations between brackets (side effects) */
1630                 **Index == 0xC7 || **Index == 0xC8 || **Index == 0xC9) &&                                /* ("<=", ">=" and "<>") */
1631                Level)                                                                   /* Not on level 0: that is handled below! */
1632       {                                                /* Expressions like 'LET A=(INKEY$="A")'; we're now between these brackets */
1633         SubType = *Type = TRUE;                                                          /* Signal: result is going to be numeric */
1634         TotalTypeKnown = TRUE;
1635         TypeKnown = FALSE;                                                               /* Start with a fresh subexpression type */
1636         (*Index) ++;
1637       }
1638       else if ((TokenMap[Keyword].TokenType != 4 && TokenMap[Keyword].TokenType != 3) ||  /* Not evaluating an expression token ? */
1639                TokenBracket)                                                             /* Or evaluating an operand of a token ? */
1640       {
1641         if (**Index == '+')                                                 /* (Can apply to both string and numeric expressions) */
1642           (*Index) ++;
1643         else if (**Index == '-' || **Index == '*' || **Index == '/' || **Index == '^')                          /* (Numeric only) */
1644         {
1645           if (!SubType)                                                                          /* Type was known to be string ? */
1646           {
1647             fprintf (ErrStream, "ERROR in line %d, statement %d - Type conflict in expression\n", BasicLineNo, StatementNo);
1648             return (FALSE);
1649           }
1650           (*Index) ++;
1651         }
1652         /* Equations and logical operators turn the total result numeric, but each subexpression may be of any type */
1653         else if ((**Index == '=' || **Index == '<' || **Index == '>' ||
1654                   **Index == 0xC7 || **Index == 0xC8 || **Index == 0xC9) &&                              /* ("<=", ">=" and "<>") */
1655                  !Level)                                                                       /* Only evaluate these on level 0! */
1656         {
1657           TotalTypeKnown = TRUE;
1658           *Type = TRUE;                                                                  /* Signal: result is going to be numeric */
1659           TypeKnown = FALSE;                                                             /* Start with a fresh subexpression type */
1660           (*Index) ++;
1661         }
1662         else
1663           More = FALSE;
1664       }
1665       else
1666         More = FALSE;
1667     }
1668   }
1669   if (!TotalTypeKnown)                                                                                   /* 'Simple' expression ? */
1670     *Type = SubType;                                                                                           /* Set return type */
1671 #ifdef __DEBUG__
1672   printf ("DEBUG - %sLeave ScanExpression, Type is %s, next char is \"%s\"\n",
1673           ListSpaces, *Type ? "NUM" : "ALPHA", TokenMap[**Index].Token);
1674   if (-- RecurseLevel > 0)
1675     memset (ListSpaces, ' ', RecurseLevel * 2);
1676   ListSpaces[RecurseLevel * 2] = '\0';
1677 #endif
1678   return (TRUE);
1679 }
1680 
HandleClass01(int BasicLineNo,int StatementNo,int Keyword,byte ** Index,bool * Type)1681 bool HandleClass01 (int BasicLineNo, int StatementNo, int Keyword, byte **Index, bool *Type)
1682 
1683 /**********************************************************************************************************************************/
1684 /* Class 1 = Used in LET. A variable is required.                                                                                 */
1685 /* `Type' is returned to handle the rest of this special statement (HandleClass02)                                                */
1686 /* This function is also used to parse the variable name for DIM and FN.                                                          */
1687 /**********************************************************************************************************************************/
1688 
1689 {
1690   int VarNameLen;
1691   int ParseArray;
1692 
1693 #ifdef __DEBUG__
1694   printf ("DEBUG - %sLine %d, statement %d, Enter Class 1, keyword \"%s\", next is \"%s\"\n",
1695           ListSpaces, BasicLineNo, StatementNo, TokenMap[Keyword].Token, TokenMap[**Index].Token);
1696 #endif
1697   if (Keyword == 0xA8 || Keyword == 0xE9)                                    /* Do not parse any bracketing if checking DIM or FN */
1698     ParseArray = -1;
1699   else if (Keyword == 0xF1)                                                             /* LET is allowed to write to a substring */
1700     ParseArray = 1;
1701   else
1702     ParseArray = 2;
1703   if (!ScanVariable (BasicLineNo, StatementNo, Keyword, Index, Type, &VarNameLen, ParseArray))
1704   {
1705     if (VarNameLen == 0)
1706       BADTOKEN ("variable", TokenMap[**Index].Token);
1707     return (FALSE);
1708   }
1709   return (TRUE);
1710 }
1711 
HandleClass02(int BasicLineNo,int StatementNo,int Keyword,byte ** Index,bool Type)1712 bool HandleClass02 (int BasicLineNo, int StatementNo, int Keyword, byte **Index, bool Type)
1713 
1714 /**********************************************************************************************************************************/
1715 /* Class 2 = Used in LET. An expression, numeric or string, must follow.                                                          */
1716 /* `Type' is the type as returned previously by the HandleClass01 call                                                            */
1717 /**********************************************************************************************************************************/
1718 
1719 {
1720   bool SubType;
1721 
1722 #ifdef __DEBUG__
1723   printf ("DEBUG - %sLine %d, statement %d, Enter Class 2, keyword \"%s\", next is \"%s\"\n",
1724           ListSpaces, BasicLineNo, StatementNo, TokenMap[Keyword].Token, TokenMap[**Index].Token);
1725 #endif
1726   if (!ScanExpression (BasicLineNo, StatementNo, Keyword, Index, &SubType, 0))
1727     return (FALSE);
1728   if (SubType != Type)                                                                                              /* Must match */
1729   {
1730     fprintf (ErrStream, "ERROR in line %d, statement %d - Bad assignment expression type\n", BasicLineNo, StatementNo);
1731     return (FALSE);
1732   }
1733   return (TRUE);
1734 }
1735 
HandleClass03(int BasicLineNo,int StatementNo,int Keyword,byte ** Index)1736 bool HandleClass03 (int BasicLineNo, int StatementNo, int Keyword, byte **Index)
1737 
1738 /**********************************************************************************************************************************/
1739 /* Class 3 = A numeric expression may follow. Zero to be used in case of default.                                                 */
1740 /**********************************************************************************************************************************/
1741 
1742 {
1743 #ifdef __DEBUG__
1744   printf ("DEBUG - %sLine %d, statement %d, Enter Class 3, keyword \"%s\", next is \"%s\"\n",
1745           ListSpaces, BasicLineNo, StatementNo, TokenMap[Keyword].Token, TokenMap[**Index].Token);
1746 #endif
1747   if (**Index == ':' || **Index == 0x0D)                                                             /* No expression following ? */
1748     return (TRUE);                                                                                     /* Then we're done already */
1749   if (Keyword == 0xFD && **Index == '#')                   /* EXCEPTION: CLEAR may take a stream rather than a numeric expression */
1750   {
1751     (*Index) ++;
1752     if (!SignalInterface1 (BasicLineNo, StatementNo, 0))                                   /* (Which is Interface1/Opus specific) */
1753       return (FALSE);
1754     if (**Index == ':' || **Index == 0x0D)                                                           /* No expression following ? */
1755       return (TRUE);                                      /* (An empty stream is allowed as well - it clears all streams at once) */
1756   }
1757   return (HandleClass06 (BasicLineNo, StatementNo, Keyword, Index));                                   /* Find numeric expression */
1758 }
1759 
HandleClass04(int BasicLineNo,int StatementNo,int Keyword,byte ** Index)1760 bool HandleClass04 (int BasicLineNo, int StatementNo, int Keyword, byte **Index)
1761 
1762 /**********************************************************************************************************************************/
1763 /* Class 4 = A single character variable must follow.                                                                             */
1764 /**********************************************************************************************************************************/
1765 
1766 {
1767   bool Type;
1768   int  VarNameLen;
1769 
1770 #ifdef __DEBUG__
1771   printf ("DEBUG - %sLine %d, statement %d, Enter Class 4, keyword \"%s\", next is \"%s\"\n",
1772           ListSpaces, BasicLineNo, StatementNo, TokenMap[Keyword].Token, TokenMap[**Index].Token);
1773 #endif
1774   if (!ScanVariable (BasicLineNo, StatementNo, Keyword, Index, &Type, &VarNameLen, 0))
1775   {
1776     if (VarNameLen == 0)
1777       BADTOKEN ("variable", TokenMap[**Index].Token);
1778     return (FALSE);
1779   }
1780   if (VarNameLen != 1 || !Type)                                                  /* Not single letter or not a numeric variable ? */
1781   {
1782     fprintf (ErrStream, "ERROR in line %d, statement %d - Wrong variable type\n", BasicLineNo, StatementNo);
1783     return (FALSE);
1784   }
1785   return (TRUE);
1786 }
1787 
HandleClass05(int BasicLineNo,int StatementNo,int Keyword,byte ** Index)1788 bool HandleClass05 (int BasicLineNo, int StatementNo, int Keyword, byte **Index)
1789 
1790 /**********************************************************************************************************************************/
1791 /* Class 5 = A set of items may be given.                                                                                         */
1792 /**********************************************************************************************************************************/
1793 
1794 {
1795   bool Type;
1796   bool More = TRUE;
1797   int  VarNameLen;
1798 
1799 #ifdef __DEBUG__
1800   printf ("DEBUG - %sLine %d, statement %d, Enter Class 5, keyword \"%s\", next is \"%s\"\n",
1801           ListSpaces, BasicLineNo, StatementNo, TokenMap[Keyword].Token, TokenMap[**Index].Token);
1802 #endif
1803   while (More)
1804   {
1805     while (**Index == ';' || **Index == ',' || **Index == '\'')                              /* One of the separator characters ? */
1806       (*Index) ++;                                                                                  /* (More than one may follow) */
1807     if (**Index == ':' || **Index == 0x0D)                                                   /* End of statement or end of line ? */
1808       More = FALSE;
1809     else if (**Index == '#')                                                                                        /* A stream ? */
1810     {
1811       (*Index) ++;                                                                                    /* (Step past the '#' mark) */
1812       if (!ScanStream (BasicLineNo, StatementNo, Keyword, Index))
1813         return (FALSE);
1814     }
1815     else if (TokenMap[**Index].TokenType == 2 ||                                                          /* A colour parameter ? */
1816              **Index == 0xAD)                                                                                            /* TAB ? */
1817     {
1818       (*Index) ++;                                                                                            /* (Skip the token) */
1819       if (!HandleClass06 (BasicLineNo, StatementNo, Keyword, Index))                       /* Find parameter (numeric expression) */
1820         return (FALSE);
1821     }
1822     else if (**Index == 0xAC)                                                                                             /* AT ? */
1823     {
1824       (*Index) ++;                                                                                            /* (Skip the token) */
1825       if (!HandleClass06 (BasicLineNo, StatementNo, Keyword, Index))                 /* Find first parameter (numeric expression) */
1826         return (FALSE);
1827       if (CheckEnd (BasicLineNo, StatementNo, Index))
1828         return (FALSE);
1829       if (**Index != ',')                                                                           /* (Required separator token) */
1830       {
1831         BADTOKEN ("\",\"", TokenMap[**Index].Token);
1832         return (FALSE);
1833       }
1834       (*Index) ++;                                                                                            /* (Skip the token) */
1835       if (!HandleClass06 (BasicLineNo, StatementNo, Keyword, Index))                /* Find second parameter (numeric expression) */
1836         return (FALSE);
1837     }
1838     else if (Keyword == 0xEE && **Index == 0xCA)                                                            /* INPUT may use LINE */
1839     {
1840       (*Index) ++;                                                                                            /* (Skip the token) */
1841       if (!ScanVariable (BasicLineNo, StatementNo, Keyword, Index, &Type, &VarNameLen, 0))
1842       {
1843         if (VarNameLen == 0)
1844           BADTOKEN ("variable", TokenMap[**Index].Token);
1845         return (FALSE);
1846       }
1847       if (Type)                                                                                  /* Not a alphanumeric variable ? */
1848       {
1849         fprintf (ErrStream, "ERROR in line %d, statement %d - INPUT LINE requires an alphanumeric variable\n",
1850                  BasicLineNo, StatementNo);
1851         return (FALSE);
1852       }
1853     }
1854     else if (!ScanExpression (BasicLineNo, StatementNo, Keyword, Index, &Type, 0))                              /* Get expression */
1855       return (FALSE);
1856     if (**Index == ':' || **Index == 0x0D)                                                   /* End of statement or end of line ? */
1857       More = FALSE;
1858     if (More)
1859       if (**Index != ';' && **Index != ',' && **Index != '\'')                              /* One of the separator characters ? */
1860       {
1861         BADTOKEN ("separator \";\", \",\" or \"\'\"", TokenMap[**Index].Token);
1862         return (FALSE);
1863       }
1864   }
1865   return (TRUE);
1866 }
1867 
HandleClass06(int BasicLineNo,int StatementNo,int Keyword,byte ** Index)1868 bool HandleClass06 (int BasicLineNo, int StatementNo, int Keyword, byte **Index)
1869 
1870 /**********************************************************************************************************************************/
1871 /* Class 6 = A numeric expression must follow.                                                                                    */
1872 /**********************************************************************************************************************************/
1873 
1874 {
1875   bool Type=TRUE;
1876 
1877 #ifdef __DEBUG__
1878   printf ("DEBUG - %sLine %d, statement %d, Enter Class 6, keyword \"%s\", next is \"%s\"\n",
1879           ListSpaces, BasicLineNo, StatementNo, TokenMap[Keyword].Token, TokenMap[**Index].Token);
1880 #endif
1881   if (!ScanExpression (BasicLineNo, StatementNo, Keyword, Index, &Type, 0))                                     /* Get expression */
1882     return (FALSE);
1883   if (!Type && Keyword != 0xC0)                                                                                /* Must be numeric */
1884   {
1885     fprintf (ErrStream, "ERROR in line %d, statement %d - Expected numeric expression\n", BasicLineNo, StatementNo);
1886     return (FALSE);
1887   }
1888   return (TRUE);
1889 }
1890 
HandleClass07(int BasicLineNo,int StatementNo,int Keyword,byte ** Index)1891 bool HandleClass07 (int BasicLineNo, int StatementNo, int Keyword, byte **Index)
1892 
1893 /**********************************************************************************************************************************/
1894 /* Class 7 = Handles colour items.                                                                                                */
1895 /* Effectively the same as Class 6                                                                                                */
1896 /**********************************************************************************************************************************/
1897 
1898 {
1899 #ifdef __DEBUG__
1900   printf ("DEBUG - %sLine %d, statement %d, Enter Class 7, keyword \"%s\", next is \"%s\"\n",
1901           ListSpaces, BasicLineNo, StatementNo, TokenMap[Keyword].Token, TokenMap[**Index].Token);
1902 #endif
1903   return (HandleClass06 (BasicLineNo, StatementNo, Keyword, Index));                                   /* Find numeric expression */
1904 }
1905 
HandleClass08(int BasicLineNo,int StatementNo,int Keyword,byte ** Index)1906 bool HandleClass08 (int BasicLineNo, int StatementNo, int Keyword, byte **Index)
1907 
1908 /**********************************************************************************************************************************/
1909 /* Class 8 = Two numeric expressions, separated by a comma, must follow.                                                          */
1910 /**********************************************************************************************************************************/
1911 
1912 {
1913 #ifdef __DEBUG__
1914   printf ("DEBUG - %sLine %d, statement %d, Enter Class 8, keyword \"%s\", next is \"%s\"\n",
1915           ListSpaces, BasicLineNo, StatementNo, TokenMap[Keyword].Token, TokenMap[**Index].Token);
1916 #endif
1917   if (!HandleClass06 (BasicLineNo, StatementNo, Keyword, Index))                                 /* Find first numeric expression */
1918     return (FALSE);
1919   if (**Index != ',')
1920   {
1921     BADTOKEN ("\",\"", TokenMap[**Index].Token);
1922     return (FALSE);
1923   }
1924   (*Index) ++;
1925   return (HandleClass06 (BasicLineNo, StatementNo, Keyword, Index));                            /* Find second numeric expression */
1926 }
1927 
HandleClass09(int BasicLineNo,int StatementNo,int Keyword,byte ** Index)1928 bool HandleClass09 (int BasicLineNo, int StatementNo, int Keyword, byte **Index)
1929 
1930 /**********************************************************************************************************************************/
1931 /* Class 9 = As for class 8 but colour items may precede the expression.                                                          */
1932 /* Used only by PLOT and DRAW. Colour items are TokenType 2                                                                       */
1933 /**********************************************************************************************************************************/
1934 
1935 {
1936   bool CheckColour = TRUE;
1937 
1938 #ifdef __DEBUG__
1939   printf ("DEBUG - %sLine %d, statement %d, Enter Class 9, keyword \"%s\", next is \"%s\"\n",
1940           ListSpaces, BasicLineNo, StatementNo, TokenMap[Keyword].Token, TokenMap[**Index].Token);
1941 #endif
1942   while (CheckColour)
1943   {
1944     if (CheckEnd (BasicLineNo, StatementNo, Index))
1945       return (FALSE);
1946     if (TokenMap[**Index].TokenType == 2)                                                                 /* A colour parameter ? */
1947     {
1948       (*Index) ++;                                                                                              /* Skip the token */
1949       if (!HandleClass06 (BasicLineNo, StatementNo, Keyword, Index))                       /* Find parameter (numeric expression) */
1950         return (FALSE);
1951       if (CheckEnd (BasicLineNo, StatementNo, Index))
1952         return (FALSE);
1953       if (**Index != ';')                                              /* All colour parameters must be separated with semicolons */
1954       {
1955         BADTOKEN ("\";\"", TokenMap[**Index].Token);
1956         return (FALSE);
1957       }
1958       (*Index) ++;                                                                                                /* Skip the ";' */
1959     }
1960     else
1961       CheckColour = FALSE;
1962   }
1963   if (CheckEnd (BasicLineNo, StatementNo, Index))
1964     return (FALSE);
1965   return (HandleClass08 (BasicLineNo, StatementNo, Keyword, Index));
1966 }
1967 
HandleClass10(int BasicLineNo,int StatementNo,int Keyword,byte ** Index)1968 bool HandleClass10 (int BasicLineNo, int StatementNo, int Keyword, byte **Index)
1969 
1970 /**********************************************************************************************************************************/
1971 /* Class 10 = A string expression must follow.                                                                                    */
1972 /**********************************************************************************************************************************/
1973 
1974 {
1975   bool Type;
1976 
1977 #ifdef __DEBUG__
1978   printf ("DEBUG - %sLine %d, statement %d, Enter Class 10, keyword \"%s\", next is \"%s\"\n",
1979           ListSpaces, BasicLineNo, StatementNo, TokenMap[Keyword].Token, TokenMap[**Index].Token);
1980 #endif
1981   if (!ScanExpression (BasicLineNo, StatementNo, Keyword, Index, &Type, 0))                                     /* Get expression */
1982     return (FALSE);
1983   if (Type)                                                                                                     /* Must be string */
1984   {
1985     fprintf (ErrStream, "ERROR in line %d, statement %d - Expected string expression\n", BasicLineNo, StatementNo);
1986     return (FALSE);
1987   }
1988   return (TRUE);
1989 }
1990 
HandleClass11(int BasicLineNo,int StatementNo,int Keyword,byte ** Index)1991 bool HandleClass11 (int BasicLineNo, int StatementNo, int Keyword, byte **Index)
1992 
1993 /**********************************************************************************************************************************/
1994 /* Class 11 = Handles cassette routines.                                                                                          */
1995 /**********************************************************************************************************************************/
1996 
1997 {
1998   bool Type;
1999   int  VarNameLen;
2000   int  MoveLoop;
2001   byte WhichChannel = '\0';                                                                  /* (Default is no channel; for tape) */
2002 
2003 #ifdef __DEBUG__
2004   printf ("DEBUG - %sLine %d, statement %d, Enter Class 11, keyword \"%s\", next is \"%s\"\n",
2005           ListSpaces, BasicLineNo, StatementNo, TokenMap[Keyword].Token, TokenMap[**Index].Token);
2006 #endif
2007   switch (Keyword)
2008   {
2009     case 0xEF:                                                                                                          /* (LOAD) */
2010     case 0xD6:                                                                                                        /* (VERIFY) */
2011     case 0xD5: if (**Index == '*')                                                                                     /* (MERGE) */
2012                {
2013                  (*Index) ++;
2014                  if (!ScanChannel (BasicLineNo, StatementNo, Keyword, Index, &WhichChannel))
2015                    return (FALSE);
2016                  if (WhichChannel != 'm' && WhichChannel != 'b' && WhichChannel != 'n')
2017                  {
2018                    fprintf (ErrStream, "ERROR in line %d, statement %d - You cannot LOAD/VERIFY/MERGE from the \"%s\" channel\n",
2019                             BasicLineNo, StatementNo, TokenMap[WhichChannel].Token);
2020                    return (FALSE);
2021                  }
2022                }
2023                else if (**Index == '!')                                                                        /* 128K RAM-bank ? */
2024                {
2025                  (*Index) ++;
2026                  switch (Is48KProgram)                                                           /* Then the program must be 128K */
2027                  {
2028                    case -1 : Is48KProgram = 0; break;                                                             /* Set the flag */
2029                    case  1 : fprintf (ErrStream, "ERROR - Line %d contains 128K file I/O, but the program\n"
2030                                       "also uses UDGs \'T\' and/or \'U\'\n", BasicLineNo);
2031                              return (FALSE);
2032                    case  0 : break;
2033                  }
2034                }
2035                if (WhichChannel != '\0' && WhichChannel != 'm')                         /* Not tape nor microdrive/disk channel ? */
2036                {
2037                  if (**Index != ':' && **Index != 0x0D &&                                                   /* (End of statement) */
2038                      **Index != 0xAF &&                                                                                 /* (CODE) */
2039                      **Index != 0xE4 &&                                                                                 /* (DATA) */
2040                      **Index != 0xCA &&                                                                                 /* (LINE) */
2041                      **Index != 0xAA)                                                                                /* (SCREEN$) */
2042                  {
2043                    fprintf (ErrStream, "ERROR in line %d, statement %d - The \"%s\" channel does not use filenames\n",
2044                             BasicLineNo, StatementNo, TokenMap[WhichChannel].Token);
2045                    return (FALSE);
2046                  }
2047                }
2048                else
2049                {
2050                  if (**Index == '\"')                                                                      /* Look for a filename */
2051                  {
2052                    while (**Index == '\"')            /* Concatenated strings are ok, since they allow the use of the " character */
2053                    {                                                             /* (And an empty string is allowed here as well) */
2054                      while (*(++ (*Index)) != '\"')                                                         /* Find closing quote */
2055                        if (**Index == 0x0D)                                                                      /* End of line ? */
2056                        {
2057                          fprintf (ErrStream, "ERROR in line %d, statement %d - Unexpected end of line\n", BasicLineNo, StatementNo);
2058                          return (FALSE);
2059                        }
2060                      (*Index) ++;                                                                                 /* Step past it */
2061                    }
2062                  }
2063                  else if (**Index == ':' || **Index == 0x0D ||                                              /* (End of statement) */
2064                           **Index == 0xAF ||                                                                            /* (CODE) */
2065                           **Index == 0xE4 ||                                                                            /* (DATA) */
2066                           **Index == 0xCA ||                                                                            /* (LINE) */
2067                           **Index == 0xAA)                                                                           /* (SCREEN$) */
2068                  {
2069                    BADTOKEN ("filename", TokenMap[**Index].Token);
2070                    return (FALSE);
2071                  }
2072                  else if (!HandleClass10 (BasicLineNo, StatementNo, Keyword, Index))              /* Look for a string expression */
2073                    return (FALSE);
2074                }
2075                if (**Index != ':' && **Index != 0x0D)                                       /* (Continue unless end of statement) */
2076                {
2077                  if (**Index == 0xAF)                                                                                     /* CODE */
2078                  {
2079                    if (Keyword == 0xD5)                                                                /* (We were doing MERGE ?) */
2080                    {
2081                      fprintf (ErrStream, "ERROR in line %d, statement %d - Cannot MERGE CODE\n", BasicLineNo, StatementNo);
2082                      return (FALSE);
2083                    }
2084                    (*Index) ++;
2085                    if (**Index != ':' && **Index != 0x0D)                                                   /* Optional address ? */
2086                    {
2087                      if (!HandleClass06 (BasicLineNo, StatementNo, Keyword, Index))          /* Find address (numeric expression) */
2088                        return (FALSE);
2089                      if (**Index == ',')                                                                /* Also optional length ? */
2090                      {
2091                        (*Index) ++;
2092                        if (!HandleClass06 (BasicLineNo, StatementNo, Keyword, Index))         /* Find length (numeric expression) */
2093                          return (FALSE);
2094                      }
2095                      else if (**Index != ':' && **Index != 0x0D)
2096                      {
2097                        BADTOKEN ("\",\"", TokenMap[**Index].Token);
2098                        return (FALSE);
2099                      }
2100                    }
2101                  }
2102                  else if (**Index == 0xAA)                                                                             /* SCREEN$ */
2103                    (*Index) ++;
2104                  else if (**Index == 0xE4)                                                                                /* DATA */
2105                  {
2106                    (*Index) ++;
2107                    if (CheckEnd (BasicLineNo, StatementNo, Index))
2108                      return (FALSE);
2109                    if (!ScanVariable (BasicLineNo, StatementNo, Keyword, Index, &Type, &VarNameLen, -1))
2110                    {
2111                      if (VarNameLen == 0)
2112                        BADTOKEN ("variable", TokenMap[**Index].Token);
2113                      return (FALSE);
2114                    }
2115                    if (VarNameLen != 1)                                                                    /* Not single letter ? */
2116                    {
2117                      fprintf (ErrStream, "ERROR in line %d, statement %d - Wrong variable type; must be single character\n",
2118                               BasicLineNo, StatementNo);
2119                      return (FALSE);
2120                    }
2121                    if (**Index != '(')                                         /* The variable must be followed by an empty index */
2122                    {
2123                      fprintf (ErrStream, "ERROR in line %d, statement %d - DATA requires an array\n",
2124                               BasicLineNo, StatementNo);
2125                      return (FALSE);
2126                    }
2127                    (*Index) ++;
2128                    if (**Index != ')')
2129                    {
2130                      fprintf (ErrStream, "ERROR in line %d, statement %d - DATA requires an empty array index\n",
2131                                 BasicLineNo, StatementNo);
2132                      return (FALSE);
2133                    }
2134                    (*Index) ++;
2135                  }
2136                  else
2137                  {
2138                    fprintf (ErrStream, "ERROR in line %d, statement %d - Unknown file-type \"%s\"\n",
2139                             BasicLineNo, StatementNo, TokenMap[**Index].Token);
2140                    return (FALSE);
2141                  }
2142                }
2143                break;
2144     case 0xF8: if (**Index == '*')                                                                                      /* (SAVE) */
2145                {
2146                  (*Index) ++;
2147                  if (!ScanChannel (BasicLineNo, StatementNo, Keyword, Index, &WhichChannel))
2148                    return (FALSE);
2149                  if (WhichChannel != 'm' && WhichChannel != 'b' && WhichChannel != 'n')
2150                  {
2151                    fprintf (ErrStream, "ERROR in line %d, statement %d - You cannot SAVE to the \"%s\" channel\n",
2152                             BasicLineNo, StatementNo, TokenMap[WhichChannel].Token);
2153                    return (FALSE);
2154                  }
2155                }
2156                else if (**Index == '!')                                                                        /* 128K RAM-bank ? */
2157                {
2158                  (*Index) ++;
2159                  switch (Is48KProgram)                                                           /* Then the program must be 128K */
2160                  {
2161                    case -1 : Is48KProgram = 0; break;                                                             /* Set the flag */
2162                    case  1 : fprintf (ErrStream, "ERROR - Line %d contains 128K file I/O, but the program\n"
2163                                       "also uses UDGs \'T\' and/or \'U\'\n", BasicLineNo);
2164                              return (FALSE);
2165                    case  0 : break;
2166                  }
2167                }
2168                if (WhichChannel != '\0' && WhichChannel != 'm')                         /* Not tape nor microdrive/disk channel ? */
2169                {
2170                  if (**Index != ':' && **Index != 0x0D &&                                                   /* (End of statement) */
2171                      **Index != 0xAF &&                                                                                 /* (CODE) */
2172                      **Index != 0xE4 &&                                                                                 /* (DATA) */
2173                      **Index != 0xCA &&                                                                                 /* (LINE) */
2174                      **Index != 0xAA)                                                                                /* (SCREEN$) */
2175                  {
2176                    fprintf (ErrStream, "ERROR in line %d, statement %d - The \"%s\" channel does not use filenames\n",
2177                             BasicLineNo, StatementNo, TokenMap[WhichChannel].Token);
2178                    return (FALSE);
2179                  }
2180                }
2181                else
2182                {
2183                  if (**Index == '\"')                                                                      /* Look for a filename */
2184                  {
2185                    if (*(*Index + 1) == '\"' &&                                                   /* Empty string (not allowed) ? */
2186                        *(*Index + 2) != '\"')                                    /* Concatenation - first char is a " (allowed) ? */
2187                    {
2188                      fprintf (ErrStream, "ERROR in line %d, statement %d - Empty filename not allowed\n", BasicLineNo, StatementNo);
2189                      return (FALSE);
2190                    }
2191                    while (**Index == '\"')            /* Concatenated strings are ok, since they allow the use of the " character */
2192                    {
2193                      while (*(++ (*Index)) != '\"')                                                         /* Find closing quote */
2194                        if (**Index == 0x0D)                                                                      /* End of line ? */
2195                        {
2196                          fprintf (ErrStream, "ERROR in line %d, statement %d - Unexpected end of line\n", BasicLineNo, StatementNo);
2197                          return (FALSE);
2198                        }
2199                      (*Index) ++;                                                                                 /* Step past it */
2200                    }
2201                  }
2202                  else if (**Index == ':' || **Index == 0x0D ||                                              /* (End of statement) */
2203                           **Index == 0xAF ||                                                                            /* (CODE) */
2204                           **Index == 0xE4 ||                                                                            /* (DATA) */
2205                           **Index == 0xCA ||                                                                            /* (LINE) */
2206                           **Index == 0xAA)                                                                           /* (SCREEN$) */
2207                  {
2208                    BADTOKEN ("filename", TokenMap[**Index].Token);
2209                    return (FALSE);
2210                  }
2211                  else if (!HandleClass10 (BasicLineNo, StatementNo, Keyword, Index))              /* Look for a string expression */
2212                    return (FALSE);
2213                }
2214                if (**Index != ':' && **Index != 0x0D)                                       /* (Continue unless end of statement) */
2215                {
2216                  if (**Index == 0xAF)                                                                                     /* CODE */
2217                  {
2218                    (*Index) ++;
2219                    if (!HandleClass06 (BasicLineNo, StatementNo, Keyword, Index))            /* Find address (numeric expression) */
2220                      return (FALSE);
2221                    if (**Index != ',')
2222                    {
2223                      fprintf (ErrStream, "ERROR in line %d, statement %d - %s CODE requires both address and length\n",
2224                               BasicLineNo, StatementNo, TokenMap[Keyword].Token);
2225                      return (FALSE);
2226                    }
2227                    (*Index) ++;
2228                    if (!HandleClass06 (BasicLineNo, StatementNo, Keyword, Index))             /* Find length (numeric expression) */
2229                      return (FALSE);
2230                  }
2231                  else if (**Index == 0xE4)                                                                                /* DATA */
2232                  {
2233                    (*Index) ++;
2234                    if (CheckEnd (BasicLineNo, StatementNo, Index))
2235                      return (FALSE);
2236                    if (!ScanVariable (BasicLineNo, StatementNo, Keyword, Index, &Type, &VarNameLen, -1))
2237                    {
2238                      if (VarNameLen == 0)
2239                        BADTOKEN ("variable", TokenMap[**Index].Token);
2240                      return (FALSE);
2241                    }
2242                    if (VarNameLen != 1)                                                                    /* Not single letter ? */
2243                    {
2244                      fprintf (ErrStream, "ERROR in line %d, statement %d - Wrong variable type; must be single character\n",
2245                               BasicLineNo, StatementNo);
2246                      return (FALSE);
2247                    }
2248                    if (**Index != '(')                                         /* The variable must be followed by an empty index */
2249                    {
2250                      fprintf (ErrStream, "ERROR in line %d, statement %d - DATA requires an array\n",
2251                               BasicLineNo, StatementNo);
2252                      return (FALSE);
2253                    }
2254                    (*Index) ++;
2255                    if (**Index != ')')
2256                    {
2257                      fprintf (ErrStream, "ERROR in line %d, statement %d - DATA requires an empty array index\n",
2258                               BasicLineNo, StatementNo);
2259                      return (FALSE);
2260                    }
2261                    (*Index) ++;
2262                  }
2263                  else if (**Index == 0xAA)                                                                             /* SCREEN$ */
2264                    (*Index) ++;
2265                  else if (**Index == 0xCA)                                                                                /* LINE */
2266                  {
2267                    (*Index) ++;
2268                    if (!HandleClass06 (BasicLineNo, StatementNo, Keyword, Index))      /* Find starting line (numeric expression) */
2269                      return (FALSE);
2270                  }
2271                  else
2272                  {
2273                    fprintf (ErrStream, "ERROR in line %d, statement %d - Unknown file-type \"%s\"\n",
2274                             BasicLineNo, StatementNo, TokenMap[**Index].Token);
2275                    return (FALSE);
2276                  }
2277                }
2278                break;
2279     case 0xCF: if (!SignalInterface1 (BasicLineNo, StatementNo, 0))                                                      /* (CAT) */
2280                  return (FALSE);
2281                if (**Index == '#')                                                       /* A stream may precede the drive number */
2282                {
2283                  (*Index) ++;
2284                  if (!ScanStream (BasicLineNo, StatementNo, Keyword, Index))
2285                    return (FALSE);
2286                  if (**Index != ',')                                                                /* (Required separator token) */
2287                  {
2288                    BADTOKEN ("\",\"", TokenMap[**Index].Token);
2289                    return (FALSE);
2290                  }
2291                }
2292                if (!HandleClass06 (BasicLineNo, StatementNo, Keyword, Index))           /* Find drive number (numeric expression) */
2293                  return (FALSE);
2294                break;
2295     case 0xD0: if (!ScanChannel (BasicLineNo, StatementNo, Keyword, Index, &WhichChannel))                            /* (FORMAT) */
2296                  return (FALSE);
2297                switch (WhichChannel)
2298                {
2299                  case 'm' : if (CheckEnd (BasicLineNo, StatementNo, Index))         /* "m" requires an additional new volume name */
2300                               return (FALSE);
2301                             if (**Index == '\"')                                                        /* Look for a volume name */
2302                             {
2303                               if (*(*Index + 1) == '\"' &&                                        /* Empty string (not allowed) ? */
2304                                   *(*Index + 2) != '\"')                         /* Concatenation - first char is a " (allowed) ? */
2305                               {
2306                                 fprintf (ErrStream, "ERROR in line %d, statement %d - Empty volume name not allowed\n",
2307                                          BasicLineNo, StatementNo);
2308                                 return (FALSE);
2309                               }
2310                               while (**Index == '\"') /* Concatenated strings are ok, since they allow the use of the " character */
2311                               {
2312                                 while (*(++ (*Index)) != '\"')                                              /* Find closing quote */
2313                                   if (**Index == 0x0D)                                                           /* End of line ? */
2314                                   {
2315                                     fprintf (ErrStream, "ERROR in line %d, statement %d - Unexpected end of line\n",
2316                                              BasicLineNo, StatementNo);
2317                                     return (FALSE);
2318                                   }
2319                                 (*Index) ++;                                                                      /* Step past it */
2320                               }
2321                             }
2322                             else if (!HandleClass10 (BasicLineNo, StatementNo, Keyword, Index))   /* Look for a string expression */
2323                               return (FALSE);
2324                             break;
2325                  case 't' :                                                 /* The port channels requires an additional baud rate */
2326                  case 'b' :
2327                  case 'j' : if (**Index != ';')                   /* The joystick channel requires a operand to turn it on or off */
2328                             {
2329                               BADTOKEN ("\";\"", TokenMap[**Index].Token);
2330                               return (FALSE);
2331                             }
2332                             (*Index) ++;
2333                             if (CheckEnd (BasicLineNo, StatementNo, Index))
2334                               return (FALSE);
2335                             if (!HandleClass06 (BasicLineNo, StatementNo, Keyword, Index))       /* Look for a numeric expression */
2336                               return (FALSE);
2337                             break;
2338                  default  : fprintf (ErrStream, "ERROR in line %d, statement %d - You cannot FORMAT from the \"%s\" channel\n",
2339                                      BasicLineNo, StatementNo, TokenMap[WhichChannel].Token);
2340                             return (FALSE);
2341                }
2342                break;
2343     case 0xD1: for (MoveLoop = 0 ; MoveLoop < 2 ; MoveLoop ++)                                                          /* (MOVE) */
2344                {
2345                  if (**Index == '#')
2346                  {
2347                    (*Index) ++;                                                                       /* (Step past the '#' mark) */
2348                    if (!ScanStream (BasicLineNo, StatementNo, Keyword, Index))
2349                      return (FALSE);
2350                  }
2351                  else
2352                  {
2353                    if (!ScanChannel (BasicLineNo, StatementNo, Keyword, Index, &WhichChannel))
2354                      return (FALSE);
2355                    switch (WhichChannel)
2356                    {
2357                      case 'm' : if (CheckEnd (BasicLineNo, StatementNo, Index))            /* "m" requires an additional filename */
2358                                   return (FALSE);
2359                                 if (**Index == '\"')                                                       /* Look for a filename */
2360                                 {
2361                                   if (*(*Index + 1) == '\"' &&                                    /* Empty string (not allowed) ? */
2362                                       *(*Index + 2) != '\"')                     /* Concatenation - first char is a " (allowed) ? */
2363                                   {
2364                                     fprintf (ErrStream, "ERROR in line %d, statement %d - Empty filename not allowed\n",
2365                                              BasicLineNo, StatementNo);
2366                                     return (FALSE);
2367                                   }
2368                                   while (**Index == '\"')
2369                                   {
2370                                     while (*(++ (*Index)) != '\"')
2371                                       if (**Index == 0x0D)
2372                                       {
2373                                         fprintf (ErrStream, "ERROR in line %d, statement %d - Unexpected end of line\n",
2374                                                  BasicLineNo, StatementNo);
2375                                         return (FALSE);
2376                                       }
2377                                     (*Index) ++;
2378                                   }
2379                                 }
2380                                 else if (!HandleClass10 (BasicLineNo, StatementNo, Keyword, Index))
2381                                   return (FALSE);
2382                                 break;
2383                      case 't' :
2384                      case 'b' :
2385                      case 'n' :
2386                      case 'd' : break;                                       /* All these are okay and don't use extra parameters */
2387                      case 's' : if (MoveLoop == 0)                                               /* The "s" channel is write-only */
2388                                 {
2389                                   fprintf (ErrStream, "ERROR in line %d, statement %d - You cannot MOVE from the \"s\" channel\n",
2390                                          BasicLineNo, StatementNo);
2391                                   return (FALSE);
2392                                 }
2393                                 break;
2394                      case 'k' : if (MoveLoop == 1)                                                /* The "k" channel is read-only */
2395                                 {
2396                                   fprintf (ErrStream, "ERROR in line %d, statement %d - You cannot MOVE to the \"k\" channel\n",
2397                                          BasicLineNo, StatementNo);
2398                                   return (FALSE);
2399                                 }
2400                                 break;
2401                      default  : fprintf (ErrStream, "ERROR in line %d, statement %d - You cannot MOVE from/to the \"%s\" channel\n",
2402                                          BasicLineNo, StatementNo, TokenMap[WhichChannel].Token);
2403                                 return (FALSE);
2404                    }
2405                  }
2406                  if (MoveLoop == 0)
2407                  {
2408                    if (**Index != 0xCC)                                                                    /* Required token 'TO' */
2409                    {
2410                      BADTOKEN ("\"TO\"", TokenMap[**Index].Token);
2411                      return (FALSE);
2412                    }
2413                    (*Index) ++;
2414                  }
2415                }
2416                break;
2417     case 0xD2: if (**Index == '!')                                                                                     /* (ERASE) */
2418                {                                                                                               /* 128K RAM-bank ? */
2419                  (*Index) ++;
2420                  switch (Is48KProgram)                                                           /* Then the program must be 128K */
2421                  {
2422                    case -1 : Is48KProgram = 0; break;                                                             /* Set the flag */
2423                    case  1 : fprintf (ErrStream, "ERROR - Line %d contains 128K file I/O, but the program\n"
2424                                       "also uses UDGs \'T\' and/or \'U\'\n", BasicLineNo);
2425                              return (FALSE);
2426                    case  0 : break;
2427                  }
2428                }
2429                else
2430                {
2431                  if (!ScanChannel (BasicLineNo, StatementNo, Keyword, Index, &WhichChannel))
2432                    return (FALSE);
2433                  if (WhichChannel != 'm')
2434                  {
2435                    fprintf (ErrStream, "ERROR in line %d, statement %d - You can only ERASE from the ! or \"m\" channel\n",
2436                             BasicLineNo, StatementNo);
2437                    return (FALSE);
2438                  }
2439                }
2440                if (CheckEnd (BasicLineNo, StatementNo, Index))                                    /* Additional filename required */
2441                  return (FALSE);
2442                if (**Index == '\"')                                                                        /* Look for a filename */
2443                {
2444                  if (*(*Index + 1) == '\"' &&                                                     /* Empty string (not allowed) ? */
2445                      *(*Index + 2) != '\"')                                      /* Concatenation - first char is a " (allowed) ? */
2446                  {
2447                    fprintf (ErrStream, "ERROR in line %d, statement %d - Empty filename not allowed\n",
2448                             BasicLineNo, StatementNo);
2449                    return (FALSE);
2450                  }
2451                  while (**Index == '\"')
2452                  {
2453                    while (*(++ (*Index)) != '\"')
2454                      if (**Index == 0x0D)
2455                      {
2456                        fprintf (ErrStream, "ERROR in line %d, statement %d - Unexpected end of line\n",
2457                                 BasicLineNo, StatementNo);
2458                        return (FALSE);
2459                      }
2460                    (*Index) ++;
2461                  }
2462                }
2463                else if (!HandleClass10 (BasicLineNo, StatementNo, Keyword, Index))
2464                  return (FALSE);
2465                break;
2466     case 0xD3: if (!ScanStream (BasicLineNo, StatementNo, Keyword, Index))                                            /* (OPEN #) */
2467                  return (FALSE);
2468                if (**Index != ';' && **Index != ',')                                                          /* (Required token) */
2469                {
2470                  BADTOKEN ("\";\"", TokenMap[**Index].Token);
2471                  return (FALSE);
2472                }
2473                (*Index) ++;
2474                if (!ScanChannel (BasicLineNo, StatementNo, Keyword, Index, &WhichChannel))
2475                  return (FALSE);
2476                switch (WhichChannel)
2477                {
2478                  case 'm' : if (CheckEnd (BasicLineNo, StatementNo, Index))                /* "m" requires an additional filename */
2479                               return (FALSE);
2480                             if (**Index == '\"')                                                           /* Look for a filename */
2481                             {
2482                               if (*(*Index + 1) == '\"' &&                                        /* Empty string (not allowed) ? */
2483                                   *(*Index + 2) != '\"')                         /* Concatenation - first char is a " (allowed) ? */
2484                               {
2485                                 fprintf (ErrStream, "ERROR in line %d, statement %d - Empty filename not allowed\n",
2486                                          BasicLineNo, StatementNo);
2487                                 return (FALSE);
2488                               }
2489                               while (**Index == '\"')
2490                               {
2491                                 while (*(++ (*Index)) != '\"')
2492                                   if (**Index == 0x0D)
2493                                   {
2494                                     fprintf (ErrStream, "ERROR in line %d, statement %d - Unexpected end of line\n",
2495                                              BasicLineNo, StatementNo);
2496                                     return (FALSE);
2497                                   }
2498                                 (*Index) ++;
2499                               }
2500                             }
2501                             else if (!HandleClass10 (BasicLineNo, StatementNo, Keyword, Index))
2502                               return (FALSE);
2503                             break;
2504                  case 's' :
2505                  case 'k' :
2506                  case 'p' :
2507                  case 't' :
2508                  case 'b' :
2509                  case 'n' :
2510                  case 0xAF:
2511                  case 0xCF:
2512                  case '#' : break;                                           /* All these are okay and don't use extra parameters */
2513                  default  : fprintf (ErrStream, "ERROR in line %d, statement %d - You cannot attach a stream to the \"%s\" "
2514                                      "channel\n", BasicLineNo, StatementNo, TokenMap[WhichChannel].Token);
2515                             return (FALSE);
2516                }
2517                if (**Index != ':' && **Index != 0x0D)                                       /* (Continue unless end of statement) */
2518                {
2519                  if (**Index == 0xBF)                                                                                       /* IN */
2520                  {
2521                    (*Index) ++;
2522                    if (!SignalInterface1 (BasicLineNo, StatementNo, 2))                                  /* This is Opus specific */
2523                      return (FALSE);
2524                  }
2525                  else if (**Index == 0xDF ||                                                                               /* OUT */
2526                           **Index == 0xB9)                                                                                 /* EXP */
2527                  {
2528                    (*Index) ++;
2529                    if (!SignalInterface1 (BasicLineNo, StatementNo, 2))                                  /* This is Opus specific */
2530                      return (FALSE);
2531                    if (!HandleClass06 (BasicLineNo, StatementNo, Keyword, Index))                      /* Find numeric expression */
2532                      return (FALSE);
2533                  }
2534                  else if (**Index == 0xA5)                                                                                 /* RND */
2535                  {
2536                    (*Index) ++;
2537                    if (!SignalInterface1 (BasicLineNo, StatementNo, 2))                                  /* This is Opus specific */
2538                      return (FALSE);
2539                    if (!HandleClass06 (BasicLineNo, StatementNo, Keyword, Index))                      /* Find numeric expression */
2540                      return (FALSE);
2541                    if (**Index == ',')                                                         /* RND may take a second parameter */
2542                    {
2543                      (*Index) ++;
2544                      if (!HandleClass06 (BasicLineNo, StatementNo, Keyword, Index))
2545                        return (FALSE);
2546                    }
2547                  }
2548                }
2549                break;
2550     case 0xD4: if (!ScanStream (BasicLineNo, StatementNo, Keyword, Index))                                           /* (CLOSE #) */
2551                  return (FALSE);
2552                break;
2553   }
2554   return (TRUE);
2555 }
2556 
HandleClass12(int BasicLineNo,int StatementNo,int Keyword,byte ** Index)2557 bool HandleClass12 (int BasicLineNo, int StatementNo, int Keyword, byte **Index)
2558 
2559 /**********************************************************************************************************************************/
2560 /* Class 12 = One or more string expressions, separated by commas, must follow.                                                   */
2561 /**********************************************************************************************************************************/
2562 
2563 {
2564   bool Type;
2565   bool More = TRUE;
2566 
2567 #ifdef __DEBUG__
2568   printf ("DEBUG - %sLine %d, statement %d, Enter Class 12, keyword \"%s\", next is \"%s\"\n",
2569           ListSpaces, BasicLineNo, StatementNo, TokenMap[Keyword].Token, TokenMap[**Index].Token);
2570 #endif
2571   while (More)
2572   {
2573     if (!ScanExpression (BasicLineNo, StatementNo, Keyword, Index, &Type, 0))                               /* Find an expression */
2574       return (FALSE);
2575     if (Type)                                                                                                   /* Must be string */
2576     {
2577       fprintf (ErrStream, "ERROR in line %d, statement %d - \"%s\" requires string parameters\n",
2578                BasicLineNo, StatementNo, TokenMap[Keyword].Token);
2579       return (FALSE);
2580     }
2581     if (**Index == ':' || **Index == 0x0D)                                                   /* End of statement or end of line ? */
2582       More = FALSE;
2583     else if (**Index == ',')                                                                                       /* Separator ? */
2584       (*Index) ++;
2585     else if (**Index != ')')
2586     {
2587       BADTOKEN ("\",\"", TokenMap[**Index].Token);
2588       return (FALSE);
2589     }
2590   }
2591   return (TRUE);
2592 }
2593 
HandleClass13(int BasicLineNo,int StatementNo,int Keyword,byte ** Index)2594 bool HandleClass13 (int BasicLineNo, int StatementNo, int Keyword, byte **Index)
2595 
2596 /**********************************************************************************************************************************/
2597 /* Class 13 = One or more expressions, separated by commas, must follow (DATA, DIM, FN)                                           */
2598 /**********************************************************************************************************************************/
2599 
2600 {
2601   bool Type;
2602   bool More = TRUE;
2603 
2604 #ifdef __DEBUG__
2605   printf ("DEBUG - %sLine %d, statement %d, Enter Class 13, keyword \"%s\", next is \"%s\"\n",
2606           ListSpaces, BasicLineNo, StatementNo, TokenMap[Keyword].Token, TokenMap[**Index].Token);
2607 #endif
2608   if (**Index == ')' && Keyword == 0xA8)                                                  /* FN requires zero or more expressions */
2609     return (TRUE);                               /* (The closing bracket is a required character and stepped over in CheckSyntax) */
2610   while (More)
2611   {
2612     if (!ScanExpression (BasicLineNo, StatementNo, Keyword, Index, &Type, 0))                               /* Find an expression */
2613       return (FALSE);                                                                              /* (Don't care about the type) */
2614     if (Keyword == 0xE9 && !Type)                                                              /* DIM requires numeric dimensions */
2615     {
2616       fprintf (ErrStream, "ERROR in line %d, statement %d - \"DIM\" requires numeric dimensions\n", BasicLineNo, StatementNo);
2617       return (FALSE);
2618     }
2619     if (Keyword == 0xE9 || Keyword == 0xA8)                                              /* FN and DIM end with a closing bracket */
2620     {
2621       if (CheckEnd (BasicLineNo, StatementNo, Index))
2622         return (FALSE);
2623       if (**Index == ')')
2624         More = FALSE;
2625     }
2626     if (**Index == ':' || **Index == 0x0D)                                                   /* End of statement or end of line ? */
2627       More = FALSE;
2628     else if (**Index == ',')                                                                                       /* Separator ? */
2629       (*Index) ++;
2630     else if (**Index != ')')
2631     {
2632       BADTOKEN ("\",\"", TokenMap[**Index].Token);
2633       return (FALSE);
2634     }
2635   }
2636   return (TRUE);
2637 }
2638 
HandleClass14(int BasicLineNo,int StatementNo,int Keyword,byte ** Index)2639 bool HandleClass14 (int BasicLineNo, int StatementNo, int Keyword, byte **Index)
2640 
2641 /**********************************************************************************************************************************/
2642 /* Class 14 = One or more variables, separated by commas, must follow (READ)                                                      */
2643 /**********************************************************************************************************************************/
2644 
2645 {
2646   bool Type;
2647   bool More = TRUE;
2648   int  VarNameLen;
2649 
2650 #ifdef __DEBUG__
2651   printf ("DEBUG - %sLine %d, statement %d, Enter Class 14, keyword \"%s\", next is \"%s\"\n",
2652           ListSpaces, BasicLineNo, StatementNo, TokenMap[Keyword].Token, TokenMap[**Index].Token);
2653 #endif
2654   while (More)
2655   {
2656     if (!ScanVariable (BasicLineNo, StatementNo, Keyword, Index, &Type, &VarNameLen, 2))                    /* We need a variable */
2657     {
2658       if (VarNameLen == 0)                                                                                    /* (Not a variable) */
2659         BADTOKEN ("variable", TokenMap[**Index].Token);
2660       return (FALSE);
2661     }
2662     if (**Index == ':' || **Index == 0x0D)                                                   /* End of statement or end of line ? */
2663       More = FALSE;
2664     else if (**Index == ',')                                                                                       /* Separator ? */
2665       (*Index) ++;
2666     else
2667     {
2668       BADTOKEN ("\",\"", TokenMap[**Index].Token);
2669       return (FALSE);
2670     }
2671   }
2672   return (TRUE);
2673 }
2674 
HandleClass15(int BasicLineNo,int StatementNo,int Keyword,byte ** Index)2675 bool HandleClass15 (int BasicLineNo, int StatementNo, int Keyword, byte **Index)
2676 
2677 /**********************************************************************************************************************************/
2678 /* Class 15 = DEF FN                                                                                                              */
2679 /**********************************************************************************************************************************/
2680 
2681 {
2682   bool Type;
2683   int  VarNameLen;
2684 
2685 #ifdef __DEBUG__
2686   printf ("DEBUG - %sLine %d, statement %d, Enter Class 15, keyword \"%s\", next is \"%s\"\n",
2687           ListSpaces, BasicLineNo, StatementNo, TokenMap[Keyword].Token, TokenMap[**Index].Token);
2688 #endif
2689   if (!ScanVariable (BasicLineNo, StatementNo, Keyword, Index, &Type, &VarNameLen, -1))
2690   {
2691     if (VarNameLen == 0)
2692       BADTOKEN ("variable", TokenMap[**Index].Token);
2693     return (FALSE);
2694   }
2695   if (VarNameLen != 1)                                                                                     /* Not single letter ? */
2696   {
2697     fprintf (ErrStream, "ERROR in line %d, statement %d - Wrong variable type; must be single character\n",
2698              BasicLineNo, StatementNo);
2699     return (FALSE);
2700   }
2701   if (**Index == '(')                                                 /* Arguments to be passed to the expression while running ? */
2702   {
2703     (*Index) ++;
2704     if (CheckEnd (BasicLineNo, StatementNo, Index))
2705       return (FALSE);
2706     if (**Index == ')')
2707     {
2708       fprintf (ErrStream, "ERROR in line %d, statement %d - Empty parameter array not allowed\n", BasicLineNo, StatementNo);
2709       return (FALSE);
2710     }
2711     while (**Index != ')')
2712     {
2713       if (!ScanVariable (BasicLineNo, StatementNo, Keyword, Index, &Type, &VarNameLen, -1))
2714       {
2715         if (VarNameLen == 0)
2716           BADTOKEN ("variable", TokenMap[**Index].Token);
2717         return (FALSE);
2718       }
2719       if (VarNameLen != 1)                                                                                 /* Not single letter ? */
2720       {
2721         fprintf (ErrStream, "ERROR in line %d, statement %d - Wrong variable type; must be single character\n",
2722                  BasicLineNo, StatementNo);
2723         return (FALSE);
2724       }
2725       if (**Index != 0x0E)                                                        /* A number (marker) must follow each parameter */
2726       {
2727         BADTOKEN ("number marker", TokenMap[**Index].Token);
2728         return (FALSE);
2729       }
2730       (*Index) ++;                                                                                              /* (Step past it) */
2731       if (CheckEnd (BasicLineNo, StatementNo, Index))
2732         return (FALSE);
2733       if (**Index != ')')
2734       {
2735         if (**Index == ',')
2736           (*Index) ++;
2737         else
2738         {
2739           BADTOKEN ("\",\"", TokenMap[**Index].Token);
2740           return (FALSE);
2741         }
2742       }
2743     }
2744     (*Index) ++;
2745   }
2746   if (CheckEnd (BasicLineNo, StatementNo, Index))
2747     return (FALSE);
2748   if (**Index != '=')
2749   {
2750     BADTOKEN ("\"=\"", TokenMap[**Index].Token);
2751     return (FALSE);
2752   }
2753   (*Index) ++;
2754   if (CheckEnd (BasicLineNo, StatementNo, Index))
2755     return (FALSE);
2756   return (ScanExpression (BasicLineNo, StatementNo, Keyword, Index, &Type, 0));                             /* Find an expression */
2757 }
2758 
CheckSyntax(int BasicLineNo,byte * Line)2759 bool CheckSyntax (int BasicLineNo, byte *Line)
2760 
2761 /**********************************************************************************************************************************/
2762 /* Pre   : `Line' points to the converted BASIC line. An initial syntax check has been done already -                             */
2763 /*         - The line number makes sense;                                                                                         */
2764 /*         - Keywords are at the beginning of each statement and not within a statement;                                          */
2765 /*         - There are less than 128 statements in the line;                                                                      */
2766 /*         - Brackets match on a per-line basis (but not necessarily on a per-statement basis!)                                   */
2767 /*         - Quotes match;                                                                                                        */
2768 /* Post  : The line has been checked against 'normal' Spectrum BASIC syntax. Extended devices that change the normal syntax       */
2769 /*         (such as Interface 1 or disk interfaces) are not understood and will generate error messages.                          */
2770 /* Import: None.                                                                                                                  */
2771 /**********************************************************************************************************************************/
2772 
2773 {
2774   byte  StrippedLine[MAXLINELENGTH + 1];
2775   byte *StrippedIndex;
2776   byte  Keyword;
2777   bool  AllOk       = TRUE;
2778   bool  VarType;
2779   int   StatementNo = 0;
2780   int   ClassIndex  = -1;
2781 
2782   StrippedIndex = &(StrippedLine[0]);
2783   while (*Line != 0x0D)                                          /* First clean up the line, dropping number expansions and trash */
2784   {
2785     switch (*Line)
2786     {
2787       case  0 :
2788       case  1 :
2789       case  2 :
2790       case  3 :
2791       case  4 :
2792       case  5 :
2793       case  6 :
2794       case  7 :
2795       case  8 :
2796       case  9 :
2797       case 10 :
2798       case 11 :
2799       case 12 :
2800       case 13 : break;
2801       case 14 : *(StrippedIndex ++) = *Line; Line += 5; break;                 /* EXCEPTION: keep the marker, but drop the number */
2802       case 15 : break;
2803       case 16 :
2804       case 17 :
2805       case 18 :
2806       case 19 :
2807       case 20 :
2808       case 21 : Line ++; break;
2809       case 22 :
2810       case 23 : Line += 2; break;
2811       case 24 :
2812       case 25 :
2813       case 26 :
2814       case 27 :
2815       case 28 :
2816       case 29 :
2817       case 30 :
2818       case 31 :
2819       case 32 : break;                                                                      /* (We don't care for spaces either!) */
2820       default : *(StrippedIndex ++) = *Line; break;                                                   /* Pass on only 'good' bits */
2821     }
2822     Line ++;
2823   }
2824   *(StrippedIndex ++) = 0x0D;
2825   *StrippedIndex = '\0';
2826   StrippedIndex = &(StrippedLine[0]);                                                                         /* Ok, here goes... */
2827   while (AllOk && *StrippedIndex != 0x0D)                                                                /* Handle each statement */
2828   {
2829     StatementNo ++;
2830     Keyword = *(StrippedIndex ++);
2831     if (Keyword == 0xEA)                                                                                               /* 'REM' ? */
2832       return (TRUE);                                                                        /* Then we're done checking this line */
2833     if (TokenMap[Keyword].TokenType != 0 && TokenMap[Keyword].TokenType != 1 && TokenMap[Keyword].TokenType != 2)     /* (Sanity) */
2834     {
2835       if (Keyword == 0xA9)                                                             /* EXCEPTION: POINT may be used as command */
2836       {
2837         if (*StrippedIndex != '#')                                                /* It must be followed by a stream in that case */
2838         {
2839           fprintf (ErrStream, "ERROR - Keyword (\"%s\") error in line %d, statement %d\n",
2840                    TokenMap[Keyword].Token, BasicLineNo, StatementNo);
2841           return (FALSE);
2842         }
2843         StrippedIndex ++;
2844         if (!ScanStream (BasicLineNo, StatementNo, Keyword, &StrippedIndex))       /* (Also signals Interface1/Opus specificness) */
2845           return (FALSE);
2846         if (*StrippedIndex != ';')
2847         {
2848           BADTOKEN ("\";\"", TokenMap[*StrippedIndex].Token);
2849           return (FALSE);
2850         }
2851         StrippedIndex ++;
2852         if (!HandleClass06 (BasicLineNo, StatementNo, Keyword, &StrippedIndex))
2853           return (FALSE);
2854       }
2855       else
2856       {
2857         fprintf (ErrStream, "ERROR - Keyword (\"%s\") error in line %d, statement %d\n",
2858                  TokenMap[Keyword].Token, BasicLineNo, StatementNo);
2859         return (FALSE);
2860       }
2861     }
2862     else
2863     {
2864       ClassIndex = -1;
2865 #ifdef __DEBUG__
2866       RecurseLevel = 0;
2867       ListSpaces[0] = '\0';
2868       printf ("DEBUG - Start Line %d, Statement %d, Keyword \"%s\"\n", BasicLineNo, StatementNo, TokenMap[Keyword].Token);
2869 #endif
2870       if ((Keyword == 0xE1 || Keyword == 0xF0) && *StrippedIndex == '#')           /* EXCEPTION: LIST and LLIST may take a stream */
2871       {
2872         StrippedIndex ++;
2873         if (!ScanStream (BasicLineNo, StatementNo, Keyword, &StrippedIndex))       /* (Also signals Interface1/Opus specificness) */
2874           return (FALSE);
2875         if (*StrippedIndex != ':' && *StrippedIndex != 0x0D)                                       /* Line number is not required */
2876         {
2877           if (*StrippedIndex != ',')
2878           {
2879             BADTOKEN ("\",\"", TokenMap[*StrippedIndex].Token);
2880             return (FALSE);
2881           }
2882           StrippedIndex ++;
2883         }
2884       }
2885       while (AllOk && TokenMap[Keyword].KeywordClass[++ ClassIndex])                               /* Handle all class parameters */
2886       {
2887         if (*StrippedIndex == 0x0D)
2888         {
2889           if (TokenMap[Keyword].KeywordClass[ClassIndex] != 3 &&                        /* Class 5 and 3 need 0 or more arguments */
2890               TokenMap[Keyword].KeywordClass[ClassIndex] != 5)
2891           {
2892             if ((Keyword == 0xEB && TokenMap[Keyword].KeywordClass[ClassIndex] == 0xCD) || /* 'FOR' doesn't need 'STEP' parameter */
2893                 (Keyword == 0xFC && TokenMap[Keyword].KeywordClass[ClassIndex] == ','))  /* 'DRAW' doesn't need a third parameter */
2894               ClassIndex ++;
2895             else
2896             {
2897               fprintf (ErrStream, "ERROR in line %d, statement %d - Unexpected end of line\n", BasicLineNo, StatementNo);
2898               AllOk = FALSE;
2899             }
2900           }
2901         }
2902         else if (TokenMap[Keyword].KeywordClass[ClassIndex] >= 32)                                   /* Required token or class ? */
2903         {
2904           if (*StrippedIndex != TokenMap[Keyword].KeywordClass[ClassIndex])                                   /* (Required token) */
2905           {
2906             if ((Keyword == 0xEB && TokenMap[Keyword].KeywordClass[ClassIndex] == 0xCD && *StrippedIndex == ':') ||
2907                 (Keyword == 0xFC && TokenMap[Keyword].KeywordClass[ClassIndex] == ',' && *StrippedIndex == ':'))
2908               ClassIndex ++;                                            /* EXCEPTION: 'FOR' does not require the 'STEP' parameter */
2909                                                                         /* EXCEPTION: 'DRAW' does not require the third parameter */
2910             else
2911             {                                                                                                /* (Token not there) */
2912               fprintf (ErrStream, "ERROR in line %d, statement %d - Expected \"%s\", but got \"%s\"\n",
2913                        BasicLineNo, StatementNo, TokenMap[TokenMap[Keyword].KeywordClass[ClassIndex]].Token,
2914                        TokenMap[*StrippedIndex].Token);
2915               AllOk = FALSE;
2916             }
2917           }
2918           else
2919             StrippedIndex ++;
2920         }
2921         else                                                                                                   /* (Command class) */
2922           switch (TokenMap[Keyword].KeywordClass[ClassIndex])
2923           {
2924             case  1 : AllOk = HandleClass01 (BasicLineNo, StatementNo, Keyword, &StrippedIndex, &VarType); break;
2925             case  2 : AllOk = HandleClass02 (BasicLineNo, StatementNo, Keyword, &StrippedIndex, VarType); break;
2926             case  3 : AllOk = HandleClass03 (BasicLineNo, StatementNo, Keyword, &StrippedIndex); break;
2927             case  4 : AllOk = HandleClass04 (BasicLineNo, StatementNo, Keyword, &StrippedIndex); break;
2928             case  5 : AllOk = HandleClass05 (BasicLineNo, StatementNo, Keyword, &StrippedIndex); break;
2929             case  6 : AllOk = HandleClass06 (BasicLineNo, StatementNo, Keyword, &StrippedIndex); break;
2930             case  7 : AllOk = HandleClass07 (BasicLineNo, StatementNo, Keyword, &StrippedIndex); break;
2931             case  8 : AllOk = HandleClass08 (BasicLineNo, StatementNo, Keyword, &StrippedIndex); break;
2932             case  9 : AllOk = HandleClass09 (BasicLineNo, StatementNo, Keyword, &StrippedIndex); break;
2933             case 10 : AllOk = HandleClass10 (BasicLineNo, StatementNo, Keyword, &StrippedIndex); break;
2934             case 11 : AllOk = HandleClass11 (BasicLineNo, StatementNo, Keyword, &StrippedIndex); break;
2935             case 12 : AllOk = HandleClass12 (BasicLineNo, StatementNo, Keyword, &StrippedIndex); break;
2936             case 13 : AllOk = HandleClass13 (BasicLineNo, StatementNo, Keyword, &StrippedIndex); break;
2937             case 14 : AllOk = HandleClass14 (BasicLineNo, StatementNo, Keyword, &StrippedIndex); break;
2938             case 15 : AllOk = HandleClass15 (BasicLineNo, StatementNo, Keyword, &StrippedIndex); break;
2939           }
2940       }
2941     }
2942     if (AllOk && Keyword != 0xFA)                                        /* Handling 'IF' and AllOk (i.e. just read the "THEN" ?) */
2943     {                                                                                        /* (Nope, go check end of statement) */
2944       if (*StrippedIndex != ':' && *StrippedIndex != 0x0D)
2945       {
2946         if (Keyword == 0xFB && *StrippedIndex == '#')                                            /* EXCEPTION: 'CLS #' is allowed */
2947         {
2948           StrippedIndex ++;
2949           if (!SignalInterface1 (BasicLineNo, StatementNo, 0))
2950             return (FALSE);
2951           if (*StrippedIndex != ':' && *StrippedIndex != 0x0D)
2952           {
2953             fprintf (ErrStream, "ERROR in line %d, statement %d - Expected end of statement, but got \"%s\"\n",
2954                      BasicLineNo, StatementNo, TokenMap[*StrippedIndex].Token);
2955             AllOk = FALSE;
2956           }
2957         }
2958         else
2959         {
2960           fprintf (ErrStream, "ERROR in line %d, statement %d - Expected end of statement, but got \"%s\"\n",
2961                    BasicLineNo, StatementNo, TokenMap[*StrippedIndex].Token);
2962           AllOk = FALSE;
2963         }
2964       }
2965     }
2966     if (AllOk && *StrippedIndex == ':')               /* (Placing this check here allows weird (but legal) construction "THEN :") */
2967     {
2968       StrippedIndex ++;
2969       while (*StrippedIndex == ':')                                              /* (More consecutive ':' separators are allowed) */
2970       {
2971         StrippedIndex ++;
2972         StatementNo ++;
2973       }
2974     }
2975   }
2976   return (AllOk);
2977 }
2978 
main(int argc,char ** argv)2979 int main (int argc, char **argv)
2980 
2981 /**********************************************************************************************************************************/
2982 /* >>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>> MAIN PROGRAM <<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<< */
2983 /* Import: MatchToken, HandleNumbers, ExpandSequences, PrepareLine, CheckSyntax.                                                  */
2984 /**********************************************************************************************************************************/
2985 
2986 {
2987   FILE *FpIn;
2988   FILE *FpOut;
2989   char  FileNameIn[256]  = "\0";
2990   char  FileNameOut[256] = "\0";
2991   char  LineIn[MAXLINELENGTH + 1];                                                           /* One line read from the ASCII file */
2992   char *BasicIndex;                                                        /* Current scan position in the (converted) ASCII line */
2993   byte *ResultIndex;                                                          /* Current write index in to the binary result line */
2994   byte  Token;
2995   int   LineCount        = 0;                                                                     /* Line count in the ASCII file */
2996   int   BasicLineNo;                                                                                 /* Current BASIC line number */
2997   int   SubLineCount;                                                                                 /* Current statement number */
2998   bool  ExpectKeyword;                                                       /* If TRUE, the next scanned token must be a keyword */
2999   bool  InString;                                                                                     /* TRUE while inside quotes */
3000   int   BracketCount     = 0;                                                               /* Match opening and closing brackets */
3001   int   AutoStart;                                                             /* Auto-start line as provided on the command line */
3002   int   ObjectLength;                                                                      /* Binary length of one converted line */
3003   int   BlockSize        = 0;                                                                      /* Total size of the TAP block */
3004   byte  Parity           = 0;                                                                             /* Overall block parity */
3005   bool  AllOk            = TRUE;
3006   bool  EndOfFile        = FALSE;
3007   bool  WriteError       = FALSE;                                                     /* Fingers crossed that this stays FALSE... */
3008   size_t Size;
3009   int   Cnt;
3010 
3011   ErrStream = stderr;
3012   Cnt = 1;
3013   for (Cnt = 1 ; Cnt < argc && AllOk; Cnt ++)                                                    /* Do all command line arguments */
3014   {
3015     if (argv[Cnt][0] == '-')
3016       switch (tolower (argv[Cnt][1]))
3017       {
3018         case 'c' : CaseIndependant = TRUE; break;
3019         case 'w' : NoWarnings = TRUE; break;
3020         case 'q' : Quiet = TRUE; break;
3021         case 'n' : DoCheckSyntax = FALSE; break;
3022         case 'e' : ErrStream = stdout; break;
3023         case 'a' : AutoStart = atoi (argv[Cnt] + 2);
3024                    if (AutoStart < 0 || AutoStart >= 10000)
3025                    {
3026                      fprintf (ErrStream, "Invalid auto-start line number %d\n", AutoStart);
3027                      exit (1);
3028                    }
3029                    TapeHeader.HStartLo = (byte)(AutoStart & 0xFF);
3030                    TapeHeader.HStartHi = (byte)(AutoStart >> 8);
3031                    break;
3032         case 's' : if (strlen (argv[Cnt] + 2) > 10)
3033                    {
3034                      fprintf (ErrStream, "Spectrum blockname too long \"%s\"\n", argv[Cnt] + 2);
3035                      exit (1);
3036                    }
3037                    strncpy (TapeHeader.HName, argv[Cnt] + 2, strlen (argv[Cnt] + 2));
3038                    break;
3039         default  : fprintf (ErrStream, "Unknown switch \'%c\'\n", argv[Cnt][1]);
3040       }
3041     else if (FileNameIn[0] == '\0')
3042       strcpy (FileNameIn, argv[Cnt]);
3043     else if (FileNameOut[0] == '\0')
3044       strcpy (FileNameOut, argv[Cnt]);
3045     else
3046       AllOk = FALSE;
3047   }
3048   if (FileNameIn[0] == '\0')                                                                         /* We do need an input file! */
3049     AllOk = FALSE;
3050   if (!Quiet || !AllOk)
3051     printf ("\nBAS2TAP v2.6 by Martijn van der Heide of ThunderWare Research Center\n\n");
3052   if (!AllOk)
3053   {
3054     printf ("Usage: BAS2TAP [-q] [-w] [-e] [-c] [-aX] [-sX] FileIn [FileOut]\n");
3055     printf ("       -q = quiet: no banner, no progress indication\n");
3056     printf ("       -w = suppress generation of warnings\n");
3057     printf ("       -e = write errors to stdout in stead of stderr channel\n");
3058     printf ("       -c = case independant tokens (be careful here!)\n");
3059     printf ("       -n = disable syntax checking\n");
3060     printf ("       -a = set auto-start line in BASIC header\n");
3061     printf ("       -s = set \"filename\" in BASIC header\n");
3062     exit (1);
3063   }
3064   if (FileNameOut[0] == '\0')
3065     strcpy (FileNameOut, FileNameIn);
3066   Size = strlen (FileNameOut);
3067   while (-- Size > 0 && FileNameOut[Size] != '.')
3068     ;
3069   if (Size == 0)                                                                                                /* No extension ? */
3070     strcat (FileNameOut, ".tap");
3071   else if (strcmp (FileNameOut + Size, ".tap") && strcmp (FileNameOut + Size, ".TAP"))
3072     strcpy (FileNameOut + Size, ".tap");
3073   if (!Quiet)
3074     printf ("Creating output file %s\n",FileNameOut);
3075   if ((FpIn = fopen (FileNameIn, "rt")) == NULL)
3076   {
3077     perror ("ERROR - Cannot open source file");
3078     exit (1);
3079   }
3080   if ((FpOut = fopen (FileNameOut, "wb")) == NULL)
3081   {
3082     perror ("ERROR - Cannot create output file");
3083     fclose (FpIn);
3084     exit (1);
3085   }
3086   Parity = TapeHeader.Flag2;
3087   if (fwrite (&TapeHeader, 1, sizeof (struct TapeHeader_s), FpOut) < sizeof (struct TapeHeader_s))
3088   { AllOk = FALSE; WriteError = TRUE; }                                                        /* Write dummy header to get space */
3089   while (AllOk && !EndOfFile)
3090   {
3091     if (fgets (LineIn, MAXLINELENGTH + 1, FpIn) != NULL)
3092     {
3093       LineCount ++;
3094       if (strlen (LineIn) >= MAXLINELENGTH)
3095       {                                                                                 /* We don't require an end-of-line marker */
3096         fprintf (ErrStream, "ERROR - Line %d too long\n", LineCount);
3097         AllOk = FALSE;
3098       }
3099       else if ((BasicLineNo = PrepareLine (LineIn, LineCount, &BasicIndex)) < 0)
3100       {
3101         if (BasicLineNo == -1)                                                                                         /* (Error) */
3102           AllOk = FALSE;
3103         else                                                                                   /* (Line should simply be skipped) */
3104           ;
3105       }
3106       else if (BasicLineNo >= 10000)
3107       {
3108         fprintf (ErrStream, "ERROR - Line number %d is larger than the maximum allowed\n", BasicLineNo);
3109         AllOk = FALSE;
3110       }
3111       else
3112       {
3113         if (!Quiet)
3114         {
3115           printf ("\rConverting line %4d -> %4d\r", LineCount, BasicLineNo);
3116           fflush (stdout);                                                      /* (Force line without end-of-line to be printed) */
3117         }
3118         InString = FALSE;
3119         ExpectKeyword = TRUE;
3120         SubLineCount = 1;
3121         ResultIndex = ResultingLine + 4;                                              /* Reserve space for line number and length */
3122         HandlingDEFFN = FALSE;
3123         while (*BasicIndex && AllOk)
3124         {
3125           if (InString)
3126           {
3127             if (*BasicIndex == '\"')
3128             {
3129               InString = FALSE;
3130               *(ResultIndex ++) = *(BasicIndex ++);
3131               while (*BasicIndex == ' ')                                                                  /* Skip trailing spaces */
3132                 BasicIndex ++;
3133             }
3134             else
3135               switch (ExpandSequences (BasicLineNo, &BasicIndex, &ResultIndex, FALSE))
3136               {
3137                 case -1 : AllOk = FALSE; break;                                                     /* (Error - already reported) */
3138                 case  0 : *(ResultIndex ++) = *(BasicIndex ++); break;                                     /* (No expansion made) */
3139                 case  1 : break;
3140               }
3141           }
3142           else if (*BasicIndex == '\"')
3143           {
3144             if (ExpectKeyword)
3145             {
3146               fprintf (ErrStream, "ERROR in line %d, statement %d - Expected keyword but got quote\n", BasicLineNo, SubLineCount);
3147               AllOk = FALSE;
3148             }
3149             else
3150             {
3151               InString = TRUE;
3152               *(ResultIndex ++) = *(BasicIndex ++);
3153             }
3154           }
3155           else if (ExpectKeyword)
3156           {
3157             switch (MatchToken (BasicLineNo, TRUE, &BasicIndex, &Token))
3158             {
3159               case -2 : AllOk = FALSE; break;                                                       /* (Error - already reported) */
3160               case -1 : fprintf (ErrStream, "ERROR in line %d, statement %d - Expected keyword but got token \"%s\"\n",
3161                                  BasicLineNo, SubLineCount, TokenMap[Token].Token);                              /* (Not keyword) */
3162                         AllOk = FALSE;
3163                         break;
3164               case  0 : fprintf (ErrStream, "ERROR in line %d, statement %d - Expected keyword but got \"%s\"\n",   /* (No match) */
3165                                  BasicLineNo, SubLineCount, TokenMap[(byte)(*BasicIndex)].Token);
3166                         AllOk = FALSE;
3167                         break;
3168               case  1 : *(ResultIndex ++) = Token;                                                             /* (Found keyword) */
3169                         if (Token != ':')                                                   /* Special exception; empty statement */
3170                           ExpectKeyword = FALSE;
3171                         if (Token == DEFFN)
3172                         {
3173                           HandlingDEFFN = TRUE;
3174                           InsideDEFFN = FALSE;
3175                         }
3176                         if (Token == 0xEA)                                                              /* Special exception; REM */
3177                           while (*BasicIndex)                                 /* Simply copy over the remaining part of the line, */
3178                                                                                        /* disregarding token or number expansions */
3179                                                                   /* As brackets aren't tested for, the match counting stops here */
3180                                                               /* (a closing bracket in a REM statement will not be seen by BASIC) */
3181                             switch (ExpandSequences (BasicLineNo, &BasicIndex, &ResultIndex, FALSE))
3182                             {
3183                               case -1 : AllOk = FALSE; break;
3184                               case  0 : *(ResultIndex ++) = *(BasicIndex ++); break;
3185                               case  1 : break;
3186                             }
3187                         break;
3188             }
3189           }
3190           else if (*BasicIndex == '(')                                                                         /* Opening bracket */
3191           {
3192             BracketCount ++;
3193             *(ResultIndex ++) = *(BasicIndex ++);
3194             if (HandlingDEFFN && !InsideDEFFN)
3195 #ifdef __DEBUG__
3196             {
3197               printf ("DEBUG - %sDEFFN, Going inside parameter list\n", ListSpaces);
3198               InsideDEFFN = TRUE;                                                           /* Signal: require special treatment! */
3199             }
3200 #else
3201               InsideDEFFN = TRUE;                                                           /* Signal: require special treatment! */
3202 #endif
3203           }
3204           else if (*BasicIndex == ')')                                                                         /* Closing bracket */
3205           {
3206             if (HandlingDEFFN && InsideDEFFN)
3207             {
3208 #ifdef __DEBUG__
3209               printf ("DEBUG - %sDEFFN, Done parameter list\n", ListSpaces);
3210               InsideDEFFN = TRUE;                                                           /* Signal: require special treatment! */
3211 #endif
3212               *(ResultIndex ++) = 0x0E;                                          /* Insert room for the evaluator (call by value) */
3213               *(ResultIndex ++) = 0x00;
3214               *(ResultIndex ++) = 0x00;
3215               *(ResultIndex ++) = 0x00;
3216               *(ResultIndex ++) = 0x00;
3217               *(ResultIndex ++) = 0x00;
3218               InsideDEFFN = FALSE;                                                               /* Mark end of special treatment */
3219               HandlingDEFFN = FALSE;                                             /* (The part after the '=' is just like eg. LET) */
3220             }
3221             if (-- BracketCount < 0)                                                        /* More closing than opening brackets */
3222             {
3223               fprintf (ErrStream, "ERROR in line %d, statement %d - Too many closing brackets\n", BasicLineNo, SubLineCount);
3224               AllOk = FALSE;
3225             }
3226             else
3227               *(ResultIndex ++) = *(BasicIndex ++);
3228           }
3229           else if (*BasicIndex == ',' && HandlingDEFFN && InsideDEFFN)
3230           {
3231 #ifdef __DEBUG__
3232             printf ("DEBUG - %sDEFFN, Done parameter; another follows\n", ListSpaces);
3233 #endif
3234             *(ResultIndex ++) = 0x0E;                                            /* Insert room for the evaluator (call by value) */
3235             *(ResultIndex ++) = 0x00;
3236             *(ResultIndex ++) = 0x00;
3237             *(ResultIndex ++) = 0x00;
3238             *(ResultIndex ++) = 0x00;
3239             *(ResultIndex ++) = 0x00;
3240             *(ResultIndex ++) = *(BasicIndex ++);                                                          /* (Copy over the ',') */
3241           }
3242           else
3243             switch (MatchToken (BasicLineNo, FALSE, &BasicIndex, &Token))
3244             {
3245               case -2 : AllOk = FALSE; break;                                                       /* (Error - already reported) */
3246               case -1 : fprintf (ErrStream, "ERROR in line %d, statement %d - Unexpected keyword \"%s\"\n",/* (Match but keyword) */
3247                                  BasicLineNo, SubLineCount, TokenMap[Token].Token);
3248                         AllOk = FALSE;
3249                         break;
3250               case  0 : switch (HandleNumbers (BasicLineNo, &BasicIndex, &ResultIndex))                             /* (No token) */
3251                         {
3252                           case 0 :  switch (ExpandSequences (BasicLineNo, &BasicIndex, &ResultIndex, TRUE))        /* (No number) */
3253                                     {
3254                                       case -1 : AllOk = FALSE; break;                               /* (Error - already reported) */
3255                                       case  0 : if (isalpha (*BasicIndex))                                 /* (No expansion made) */
3256                                                   while (isalnum (*BasicIndex))                    /* Skip full strings in one go */
3257                                                     *(ResultIndex ++) = *(BasicIndex ++);
3258                                                 else
3259                                                   *(ResultIndex ++) = *(BasicIndex ++);
3260                                                 break;
3261                                       case  1 : break;
3262                                     }
3263                                     break;
3264                           case -1 : AllOk = FALSE; break;
3265                         }
3266                         break;
3267               case  1 : *(ResultIndex ++) = Token;                                                   /* (Found token, no keyword) */
3268                         if (Token == ':' || Token == 0xCB)
3269                         {
3270                           ExpectKeyword = TRUE;
3271                           HandlingDEFFN = FALSE;
3272                           if (BracketCount != 0)                                                          /* All brackets match ? */
3273                           {
3274                             fprintf (ErrStream, "ERROR in line %d, statement %d - Too few closing brackets\n",
3275                                      BasicLineNo, SubLineCount);
3276                             AllOk = FALSE;
3277                           }
3278                           if (++ SubLineCount > 127)
3279                           {
3280                             fprintf (ErrStream, "ERROR - Line %d has too many statements\n", BasicLineNo);
3281                             AllOk = FALSE;
3282                           }
3283                         }
3284                         else if (Token == 0xC4)                                                                            /* BIN */
3285                         {
3286                           if (HandleBIN (BasicLineNo, &BasicIndex, &ResultIndex) == -1)
3287                             AllOk = FALSE;
3288                         }
3289                         break;
3290             }
3291         }
3292         *(ResultIndex ++) = 0x0D;
3293         if (AllOk && BracketCount != 0)                                                                   /* All brackets match ? */
3294         {
3295           fprintf (ErrStream, "ERROR in line %d, statement %d - Too few closing brackets\n", BasicLineNo, SubLineCount);
3296           AllOk = FALSE;
3297         }
3298         if (AllOk && DoCheckSyntax)
3299           AllOk = CheckSyntax (BasicLineNo, ResultingLine + 4);                           /* Check the syntax of the decoded line */
3300         if (AllOk)
3301         {
3302           ObjectLength = (int)(ResultIndex - ResultingLine);
3303           ResultingLine[0] = (byte)(BasicLineNo >> 8);                                             /* Line number is put reversed */
3304           ResultingLine[1] = (byte)(BasicLineNo & 0xFF);
3305           ResultingLine[2] = (byte)((ObjectLength - 4) & 0xFF);                                 /* Make sure this runs on any CPU */
3306           ResultingLine[3] = (byte)((ObjectLength - 4) >> 8);
3307           BlockSize += ObjectLength;
3308           for (Cnt = 0 ; Cnt < ObjectLength ; Cnt ++)
3309             Parity ^= ResultingLine[Cnt];
3310           if (BlockSize > 41500)                                                       /* (= 65368-23755-<some work/stack space>) */
3311           {
3312             fprintf (ErrStream, "ERROR - Object file too large at line %d!\n", BasicLineNo);
3313             AllOk = FALSE;
3314           }
3315           else
3316             if (fwrite (ResultingLine, 1, ObjectLength, FpOut) != ObjectLength)
3317             { AllOk = FALSE; WriteError = TRUE; }
3318         }
3319       }
3320     }
3321     else
3322       EndOfFile = TRUE;
3323   }
3324   if (!Quiet)
3325   {
3326     printf ("\r                                     \r");
3327     fflush (stdout);
3328   }
3329   if (!WriteError)                             /* Finish the TAP file no matter what went wrong, unless it was the writing itself */
3330   {
3331     ResultingLine[0] = Parity;                                               /* Now it's time to write the 'real' header in front */
3332     if (fwrite (ResultingLine, 1, 1, FpOut) < 1)
3333     {
3334       perror ("ERROR - Write error");
3335       fclose (FpIn);
3336       fclose (FpOut);
3337       exit (1);
3338     }
3339     TapeHeader.HLenLo = TapeHeader.HBasLenLo = (byte)(BlockSize & 0xFF);
3340     TapeHeader.HLenHi = TapeHeader.HBasLenHi = (byte)(BlockSize >> 8);
3341     TapeHeader.LenLo2 = (byte)((BlockSize + 2) & 0xFF);
3342     TapeHeader.LenHi2 = (byte)((BlockSize + 2) >> 8);
3343     Parity = 0;
3344     for (Cnt = 2 ; Cnt < 20 ; Cnt ++)
3345       Parity ^= *((byte *)&TapeHeader + Cnt);
3346     TapeHeader.Parity1 = Parity;
3347     fseek (FpOut, 0, SEEK_SET);
3348     if (fwrite (&TapeHeader, 1, sizeof (struct TapeHeader_s), FpOut) < sizeof (struct TapeHeader_s))
3349     {
3350       perror ("ERROR - Write error");
3351       exit (1);
3352     }
3353     if (!Quiet)
3354     {
3355       if (AllOk)
3356         printf ("Done! Listing contains %d %s.\n", LineCount, LineCount == 1 ? "line" : "lines");
3357       else
3358         printf ("Listing as far as done contains %d %s.\n", LineCount - 1, LineCount == 2 ? "line" : "lines");
3359       if (Is48KProgram >= 0)
3360         printf ("Note: this program can only be used in %dK mode\n", Is48KProgram ? 48 : 128);
3361       switch (UsesInterface1)
3362       {
3363         case -1 : break;                                                                                       /* Neither of them */
3364         case 0  : printf ("Note: this program requires Interface 1 or Opus Discovery\n"); break;
3365         case 1  : printf ("Note: this program requires Interface 1\n"); break;
3366         case 2  : printf ("Note: this program requires an Opus Discovery"); break;
3367       }
3368     }
3369   }
3370   else
3371     perror ("ERROR - Write error");
3372   fclose (FpIn);
3373   fclose (FpOut);
3374   return (0);                                                                                     /* (Keep weird compilers happy) */
3375 }
3376