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