aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorJuan J. Martinez <jjm@usebox.net>2023-07-29 08:12:16 +0100
committerJuan J. Martinez <jjm@usebox.net>2023-07-29 08:25:22 +0100
commitf569f70654def8f2b590fca62dbfa3e7b2b5dd8c (patch)
treeea33632775a75e9ac1caf3f12cbf09763f30dcc3
downloadsinclair-basic-main.tar.gz
sinclair-basic-main.zip
Initial importHEADmain
-rw-r--r--.gitignore5
-rw-r--r--Makefile26
-rw-r--r--README.md25
-rw-r--r--bas2tap.c3286
-rw-r--r--bas2tap.doc186
-rw-r--r--game.bas10
6 files changed, 3538 insertions, 0 deletions
diff --git a/.gitignore b/.gitignore
new file mode 100644
index 0000000..59cee7e
--- /dev/null
+++ b/.gitignore
@@ -0,0 +1,5 @@
+*.o
+*.swp
+*~
+*.tap
+bas2tap
diff --git a/Makefile b/Makefile
new file mode 100644
index 0000000..097b8f1
--- /dev/null
+++ b/Makefile
@@ -0,0 +1,26 @@
+TARGET := game
+
+CC := gcc
+CFLAGS := -Wall -O2
+LDFLAGS := -s -lm
+
+all: $(TARGET).tap
+
+zxsec: $(TARGET).tap
+ zxsec $<
+
+fuse: $(TARGET).tap
+ fuse-gtk $<
+
+$(TARGET).tap: $(TARGET).bas bas2tap
+ ./bas2tap -a -s -q $< $@
+
+bas2tap: bas2tap.c
+ $(CC) $(CFLAGS) $< -o $@ $(LDFLAGS)
+
+clean:
+ rm -f *.o bas2tap
+cleanall: clean
+ rm -f $(TARGET).tap
+
+.PHONY: all run clean cleanall
diff --git a/README.md b/README.md
new file mode 100644
index 0000000..9480ab4
--- /dev/null
+++ b/README.md
@@ -0,0 +1,25 @@
+# ZX Spectrum BASIC cross-compilation
+
+This is an easy way to write Sinclair BASIC for the ZX Spectrum using a regular
+PC thanks to BAS2TAP by M. van der Heide (see [bas2tap.doc](bas2tap.doc) for
+details).
+
+It compiles to "bytecode" that will be run by the ZX Spectrum, and not into
+machine code.
+
+Requirements:
+
+* Make (GNU Make recommended)
+* A C compiler (the Makefile expects GCC)
+* a ZX Spectrum emulator (optional; for `make fuse` -- FUSE GTK is expected)
+
+"Basically" edit `game.bas` with you editor of choice and run `make` to
+compile it to tokens in a `.tap` file that can be loaded in a ZX Spectrum
+emulator.
+
+Optionally you can run `make fuse` or `make zxsec` to load that tape on an emulator.
+
+Given that most of the functionality comes from BAS2TAP, that has a weird
+licence (that claims to be open source, but not sure it is!), this is
+distributed under the same terms of BAS2TAP.
+
diff --git a/bas2tap.c b/bas2tap.c
new file mode 100644
index 0000000..f34e687
--- /dev/null
+++ b/bas2tap.c
@@ -0,0 +1,3286 @@
+/**********************************************************************************************************************************/
+/* Module : BAS2TAP.C */
+/* Executable : BAS2TAP.EXE */
+/* Doc file : BAS2TAP.DOC */
+/* Version type : Single file */
+/* Last changed : 13-06-2003 12:00 */
+/* Update count : 14 */
+/* OS type : Generic */
+/* Watcom C = wcl386 -mf -fp3 -fpi -3r -oxnt -w4 -we bas2tap.c */
+/* MS C = cl /Ox /G2 /AS bas2tap.c /F 1000 */
+/* gcc = gcc -Wall -O2 bas2tap.c -o bas2tap -lm ; strip bas2tap */
+/* SAS/C = sc link math=ieee bas2tap.c */
+/* Libs needed : math */
+/* Description : Convert ASCII BASIC file to TAP tape image emulator file */
+/* */
+/* Notes : There's a check for a define "__DEBUG__", which generates tons of output if defined. */
+/* */
+/* Written in 1998-2003 by M. van der Heide of ThunderWare Research Center. */
+/**********************************************************************************************************************************/
+
+#include <sys/types.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <errno.h>
+#include <string.h>
+#include <ctype.h>
+#include <math.h>
+
+/**********************************************************************************************************************************/
+/* Some compilers don't define the following things, so I define them here... */
+/**********************************************************************************************************************************/
+
+#ifdef __WATCOMC__
+#define x_strnicmp(_S1,_S2,_Len) strnicmp (_S1, _S2, _Len)
+#define x_log2(_X) log2 (_X)
+#else
+int x_strnicmp (char *_S1, char *_S2, int _Len) /* Case independant partial string compare */
+{
+ for ( ; _Len && *_S1 && *_S2 && toupper (*_S1) == toupper (*_S2) ; _S1 ++, _S2 ++, _Len --)
+ ;
+ return (_Len ? (int)toupper (*_S1) - (int)toupper (*_S2) : 0);
+}
+#define x_log2(_X) (log (_X) / log (2.0)) /* If your compiler doesn't know the 'log2' function */
+#endif
+
+typedef unsigned char byte;
+#ifndef FALSE
+typedef unsigned char bool; /* If your compiler doesn't know this variable type yet */
+#define TRUE 1
+#define FALSE 0
+#endif
+
+/**********************************************************************************************************************************/
+/* Define the global variables */
+/**********************************************************************************************************************************/
+
+struct TokenMap_s
+{
+ char *Token;
+ byte TokenType;
+ /* Type 0 = No special meaning */
+ /* Type 1 = Always keyword */
+ /* Type 2 = Can be both keyword and non-keyword (colour parameters) */
+ /* Type 3 = Numeric expression token */
+ /* Type 4 = String expression token */
+ /* Type 5 = May only appear in (L)PRINT statements (AT and TAB) */
+ /* Type 6 = Type-less (normal ASCII or expression token) */
+ byte KeywordClass[8]; /* The class this keyword belongs to, as defined in the Spectrum ROM */
+ /* This table is used by expression tokens as well. Class 12 was added for this purpose */
+ /* Class 0 = No further operands */
+ /* Class 1 = Used in LET. A variable is required */
+ /* Class 2 = Used in LET. An expression, numeric or string, must follow */
+ /* Class 3 = A numeric expression may follow. Zero to be used in case of default */
+ /* Class 4 = A single character variable must follow */
+ /* Class 5 = A set of items may be given */
+ /* Class 6 = A numeric expression must follow */
+ /* Class 7 = Handles colour items */
+ /* Class 8 = Two numeric expressions, separated by a comma, must follow */
+ /* Class 9 = As for class 8 but colour items may precede the expression */
+ /* Class 10 = A string expression must follow */
+ /* Class 11 = Handles cassette routines */
+ /* The following classes are not available in the ROM but were needed */
+ /* Class 12 = One or more string expressions, separated by commas, must follow */
+ /* Class 13 = One or more expressions, separated by commas, must follow */
+ /* Class 14 = One or more variables, separated by commas, must follow (READ) */
+ /* Class 15 = DEF FN */
+} TokenMap[256] = {
+
+ /* Everything below ASCII 32 */
+ {NULL, 6, { 0 }}, {NULL, 6, { 0 }}, {NULL, 6, { 0 }}, {NULL, 6, { 0 }}, {NULL, 6, { 0 }},
+ {NULL, 6, { 0 }}, /* Print ' */
+ {NULL, 6, { 0 }}, {NULL, 6, { 0 }}, {NULL, 6, { 0 }}, {NULL, 6, { 0 }}, {NULL, 6, { 0 }}, {NULL, 6, { 0 }}, {NULL, 6, { 0 }},
+ {"(eoln)", 6, { 0 }}, /* CR */
+ {NULL, 6, { 0 }}, /* Number */
+ {NULL, 6, { 0 }},
+ {NULL, 6, { 0 }}, /* INK */
+ {NULL, 6, { 0 }}, /* PAPER */
+ {NULL, 6, { 0 }}, /* FLASH */
+ {NULL, 6, { 0 }}, /* BRIGHT */
+ {NULL, 6, { 0 }}, /* INVERSE */
+ {NULL, 6, { 0 }}, /* OVER */
+ {NULL, 6, { 0 }}, /* AT */
+ {NULL, 6, { 0 }}, /* TAB */
+ {NULL, 6, { 0 }}, {NULL, 6, { 0 }}, {NULL, 6, { 0 }}, {NULL, 6, { 0 }}, {NULL, 6, { 0 }}, {NULL, 6, { 0 }}, {NULL, 6, { 0 }},
+ {NULL, 6, { 0 }},
+
+ /* Normal ASCII set */
+ {"\x20", 6, { 0 }}, {"\x21", 6, { 0 }}, {"\x22", 6, { 0 }}, {"\x23", 6, { 0 }}, {"\x24", 6, { 0 }}, {"\x25", 6, { 0 }},
+ {"\x26", 6, { 0 }}, {"\x27", 6, { 0 }}, {"\x28", 6, { 0 }}, {"\x29", 6, { 0 }}, {"\x2A", 6, { 0 }}, {"\x2B", 6, { 0 }},
+ {"\x2C", 6, { 0 }}, {"\x2D", 6, { 0 }}, {"\x2E", 6, { 0 }}, {"\x2F", 6, { 0 }}, {"\x30", 6, { 0 }}, {"\x31", 6, { 0 }},
+ {"\x32", 6, { 0 }}, {"\x33", 6, { 0 }}, {"\x34", 6, { 0 }}, {"\x35", 6, { 0 }}, {"\x36", 6, { 0 }}, {"\x37", 6, { 0 }},
+ {"\x38", 6, { 0 }}, {"\x39", 6, { 0 }}, {"\x3A", 2, { 0 }}, {"\x3B", 6, { 0 }}, {"\x3C", 6, { 0 }}, {"\x3D", 6, { 0 }},
+ {"\x3E", 6, { 0 }}, {"\x3F", 6, { 0 }}, {"\x40", 6, { 0 }}, {"\x41", 6, { 0 }}, {"\x42", 6, { 0 }}, {"\x43", 6, { 0 }},
+ {"\x44", 6, { 0 }}, {"\x45", 6, { 0 }}, {"\x46", 6, { 0 }}, {"\x47", 6, { 0 }}, {"\x48", 6, { 0 }}, {"\x49", 6, { 0 }},
+ {"\x4A", 6, { 0 }}, {"\x4B", 6, { 0 }}, {"\x4C", 6, { 0 }}, {"\x4D", 6, { 0 }}, {"\x4E", 6, { 0 }}, {"\x4F", 6, { 0 }},
+ {"\x50", 6, { 0 }}, {"\x51", 6, { 0 }}, {"\x52", 6, { 0 }}, {"\x53", 6, { 0 }}, {"\x54", 6, { 0 }}, {"\x55", 6, { 0 }},
+ {"\x56", 6, { 0 }}, {"\x57", 6, { 0 }}, {"\x58", 6, { 0 }}, {"\x59", 6, { 0 }}, {"\x5A", 6, { 0 }}, {"\x5B", 6, { 0 }},
+ {"\x5C", 6, { 0 }}, {"\x5D", 6, { 0 }}, {"\x5E", 6, { 0 }}, {"\x5F", 6, { 0 }}, {"\x60", 6, { 0 }}, {"\x61", 6, { 0 }},
+ {"\x62", 6, { 0 }}, {"\x63", 6, { 0 }}, {"\x64", 6, { 0 }}, {"\x65", 6, { 0 }}, {"\x66", 6, { 0 }}, {"\x67", 6, { 0 }},
+ {"\x68", 6, { 0 }}, {"\x69", 6, { 0 }}, {"\x6A", 6, { 0 }}, {"\x6B", 6, { 0 }}, {"\x6C", 6, { 0 }}, {"\x6D", 6, { 0 }},
+ {"\x6E", 6, { 0 }}, {"\x6F", 6, { 0 }}, {"\x70", 6, { 0 }}, {"\x71", 6, { 0 }}, {"\x72", 6, { 0 }}, {"\x73", 6, { 0 }},
+ {"\x74", 6, { 0 }}, {"\x75", 6, { 0 }}, {"\x76", 6, { 0 }}, {"\x77", 6, { 0 }}, {"\x78", 6, { 0 }}, {"\x79", 6, { 0 }},
+ {"\x7A", 6, { 0 }}, {"\x7B", 6, { 0 }}, {"\x7C", 6, { 0 }}, {"\x7D", 6, { 0 }}, {"\x7E", 6, { 0 }}, {"\x7F", 6, { 0 }},
+
+ /* Block graphics without shift */
+ {"\x80", 6, { 0 }}, {"\x81", 6, { 0 }}, {"\x82", 6, { 0 }}, {"\x83", 6, { 0 }}, {"\x84", 6, { 0 }}, {"\x85", 6, { 0 }},
+ {"\x86", 6, { 0 }}, {"\x87", 6, { 0 }},
+
+ /* Block graphics with shift */
+ {"\x88", 6, { 0 }}, {"\x89", 6, { 0 }}, {"\x8A", 6, { 0 }}, {"\x8B", 6, { 0 }}, {"\x8C", 6, { 0 }}, {"\x8D", 6, { 0 }},
+ {"\x8E", 6, { 0 }}, {"\x8F", 6, { 0 }},
+
+ /* UDGs */
+ {"\x90", 6, { 0 }}, {"\x91", 6, { 0 }}, {"\x92", 6, { 0 }}, {"\x93", 6, { 0 }}, {"\x94", 6, { 0 }}, {"\x95", 6, { 0 }},
+ {"\x96", 6, { 0 }}, {"\x97", 6, { 0 }}, {"\x98", 6, { 0 }}, {"\x99", 6, { 0 }}, {"\x9A", 6, { 0 }}, {"\x9B", 6, { 0 }},
+ {"\x9C", 6, { 0 }}, {"\x9D", 6, { 0 }}, {"\x9E", 6, { 0 }}, {"\x9F", 6, { 0 }}, {"\xA0", 6, { 0 }}, {"\xA1", 6, { 0 }},
+ {"\xA2", 6, { 0 }},
+
+ {"SPECTRUM", 1, { 0 }}, /* For Spectrum 128 */
+ {"PLAY", 1, { 12 }},
+
+ /* BASIC tokens - expression */
+ {"RND", 3, { 0 }},
+ {"INKEY$", 4, { 0 }},
+ {"PI", 3, { 0 }},
+ {"FN", 3, { 1, '(', 13, ')', 0 }},
+ {"POINT", 3, { '(', 8, ')', 0 }},
+ {"SCREEN$", 4, { '(', 8, ')', 0 }},
+ {"ATTR", 3, { '(', 8, ')', 0 }},
+ {"AT", 5, { 8, 0 }},
+ {"TAB", 5, { 6, 0 }},
+ {"VAL$", 4, { 10, 0 }},
+ {"CODE", 3, { 10, 0 }},
+ {"VAL", 3, { 10, 0 }},
+ {"LEN", 3, { 10, 0 }},
+ {"SIN", 3, { 6, 0 }},
+ {"COS", 3, { 6, 0 }},
+ {"TAN", 3, { 6, 0 }},
+ {"ASN", 3, { 6, 0 }},
+ {"ACS", 3, { 6, 0 }},
+ {"ATN", 3, { 6, 0 }},
+ {"LN", 3, { 6, 0 }},
+ {"EXP", 3, { 6, 0 }},
+ {"INT", 3, { 6, 0 }},
+ {"SQR", 3, { 6, 0 }},
+ {"SGN", 3, { 6, 0 }},
+ {"ABS", 3, { 6, 0 }},
+ {"PEEK", 3, { 6, 0 }},
+ {"IN", 3, { 6, 0 }},
+ {"USR", 3, { 6, 0 }},
+ {"STR$", 4, { 6, 0 }},
+ {"CHR$", 4, { 6, 0 }},
+ {"NOT", 3, { 6, 0 }},
+ {"BIN", 3, { 3, 0 }},
+ {"OR", 6, { 5, 0 }}, /* -\ */
+ {"AND", 6, { 5, 0 }}, /* | */
+ {"<=", 6, { 5, 0 }}, /* | These are handled directly within ScanExpression */
+ {">=", 6, { 5, 0 }}, /* | */
+ {"<>", 6, { 5, 0 }}, /* -/ */
+ {"LINE", 6, { 0 }},
+ {"THEN", 6, { 0 }},
+ {"TO", 6, { 0 }},
+ {"STEP", 6, { 0 }},
+
+ /* BASIC tokens - keywords */
+ {"DEF FN", 1, { 15, 0 }}, /* Special treatment - insertion of call-by-value room required for the evaluator */
+ {"CAT", 1, { 11, 0 }},
+ {"FORMAT", 1, { 11, 0 }},
+ {"MOVE", 1, { 11, 0 }},
+ {"ERASE", 1, { 11, 0 }},
+ {"OPEN #", 1, { 11, 0 }},
+ {"CLOSE #", 1, { 11, 0 }},
+ {"MERGE", 1, { 11, 0 }},
+ {"VERIFY", 1, { 11, 0 }},
+ {"BEEP", 1, { 8, 0 }},
+ {"CIRCLE", 1, { 9, ',', 6, 0 }},
+ {"INK", 2, { 7, 0 }},
+ {"PAPER", 2, { 7, 0 }},
+ {"FLASH", 2, { 7, 0 }},
+ {"BRIGHT", 2, { 7, 0 }},
+ {"INVERSE", 2, { 7, 0 }},
+ {"OVER", 2, { 7, 0 }},
+ {"OUT", 1, { 8, 0 }},
+ {"LPRINT", 1, { 5, 0 }},
+ {"LLIST", 1, { 3, 0 }},
+ {"STOP", 1, { 0 }},
+ {"READ", 1, { 14, 0 }},
+ {"DATA", 2, { 13, 0 }},
+ {"RESTORE", 1, { 3, 0 }},
+ {"NEW", 1, { 0 }},
+ {"BORDER", 1, { 6, 0 }},
+ {"CONTINUE", 1, { 0 }},
+ {"DIM", 1, { 1, '(', 13, ')', 0 }},
+ {"REM", 1, { 5, 0 }}, /* (Special: taken out separately) */
+ {"FOR", 1, { 4, '=', 6, 0xCC, 6, 0xCD, 6, 0 }}, /* (Special: STEP (0xCD) is not required) */
+ {"GO TO", 1, { 6, 0 }},
+ {"GO SUB", 1, { 6, 0 }},
+ {"INPUT", 1, { 5, 0 }},
+ {"LOAD", 1, { 11, 0 }},
+ {"LIST", 1, { 3, 0 }},
+ {"LET", 1, { 1, '=', 2, 0 }},
+ {"PAUSE", 1, { 6, 0 }},
+ {"NEXT", 1, { 4, 0 }},
+ {"POKE", 1, { 8, 0 }},
+ {"PRINT", 1, { 5, 0 }},
+ {"PLOT", 1, { 9, 0 }},
+ {"RUN", 1, { 3, 0 }},
+ {"SAVE", 1, { 11, 0 }},
+ {"RANDOMIZE", 1, { 3, 0 }},
+ {"IF", 1, { 6, 0xCB, 0 }},
+ {"CLS", 1, { 0 }},
+ {"DRAW", 1, { 9, ',', 6, 0 }},
+ {"CLEAR", 1, { 3, 0 }},
+ {"RETURN", 1, { 0 }},
+ {"COPY", 1, { 0 }}};
+
+#define MAXLINELENGTH 1024
+
+char ConvertedSpectrumLine[MAXLINELENGTH + 1];
+byte ResultingLine[MAXLINELENGTH + 1];
+
+struct TapeHeader_s
+{
+ byte LenLo1;
+ byte LenHi1;
+ byte Flag1;
+ byte HType;
+ char HName[10];
+ byte HLenLo;
+ byte HLenHi;
+ byte HStartLo;
+ byte HStartHi;
+ byte HBasLenLo;
+ byte HBasLenHi;
+ byte Parity1;
+ byte LenLo2;
+ byte LenHi2;
+ byte Flag2;
+} TapeHeader = {19, 0, /* Len header */
+ 0, /* Flag header */
+ 0, {32, 32, 32, 32, 32, 32, 32, 32, 32, 32}, 0, 0, 0, 128, 0, 0, /* The header itself */
+ 0, /* Parity header */
+ 0, 0, /* Len converted BASIC */
+ 255}; /* Flag converted BASIC */
+
+int Is48KProgram = -1; /* -1 = unknown */
+ /* 1 = 48K */
+ /* 0 = 128K */
+int UsesInterface1 = -1; /* -1 = unknown */
+ /* 0 = either Interface1 or Opus Discovery */
+ /* 1 = Interface1 */
+ /* 2 = Opus Discovery */
+bool CaseIndependant = FALSE;
+bool Quiet = FALSE; /* Suppress banner and progress indication if TRUE */
+bool NoWarnings = FALSE; /* Suppress warnings if TRUE */
+bool DoCheckSyntax = TRUE;
+bool TokenBracket = FALSE;
+bool HandlingDEFFN = FALSE; /* Exceptional instruction */
+bool InsideDEFFN = FALSE;
+#define DEFFN 0xCE
+FILE *ErrStream;
+
+/**********************************************************************************************************************************/
+/* Let's be lazy and define a very commonly used error message.... */
+/**********************************************************************************************************************************/
+
+#define BADTOKEN(_Exp,_Got) fprintf (ErrStream, "ERROR in line %d, statement %d - Expected %s, but got \"%s\"\n", \
+ BasicLineNo, StatementNo, _Exp, _Got)
+
+/**********************************************************************************************************************************/
+/* And let's generate tons of debugging info too.... */
+/**********************************************************************************************************************************/
+
+#ifdef __DEBUG__
+ char ListSpaces[20];
+ int RecurseLevel;
+#endif
+
+/**********************************************************************************************************************************/
+/* Prototype all functions */
+/**********************************************************************************************************************************/
+
+int GetLineNumber (char **FirstAfter);
+int MatchToken (int BasicLineNo, bool WantKeyword, char **LineIndex, byte *Token);
+int HandleNumbers (int BasicLineNo, char **BasicLine, byte **SpectrumLine);
+int ExpandSequences (int BasicLineNo, char **BasicLine, byte **SpectrumLine, bool StripSpaces);
+int PrepareLine (char *LineIn, int FileLineNo, char **FirstToken);
+bool ScanVariable (int BasicLineNo, int StatementNo, int Keyword, byte **Index, bool *Type, int *NameLen, int AllowSlicing);
+bool SliceDirectString (int BasicLineNo, int StatementNo, int Keyword, byte **Index);
+bool ScanStream (int BasicLineNo, int StatementNo, int Keyword, byte **Index);
+bool ScanChannel (int BasicLineNo, int StatementNo, int Keyword, byte **Index, byte *WhichChannel);
+bool SignalInterface1 (int BasicLineNo, int StatementNo, int NewMode);
+bool CheckEnd (int BasicLineNo, int StatementNo, byte **Index);
+bool ScanExpression (int BasicLineNo, int StatementNo, int Keyword, byte **Index, bool *Type, int Level);
+bool HandleClass01 (int BasicLineNo, int StatementNo, int Keyword, byte **Index, bool *Type);
+bool HandleClass02 (int BasicLineNo, int StatementNo, int Keyword, byte **Index, bool Type);
+bool HandleClass03 (int BasicLineNo, int StatementNo, int Keyword, byte **Index);
+bool HandleClass04 (int BasicLineNo, int StatementNo, int Keyword, byte **Index);
+bool HandleClass05 (int BasicLineNo, int StatementNo, int Keyword, byte **Index);
+bool HandleClass06 (int BasicLineNo, int StatementNo, int Keyword, byte **Index);
+bool HandleClass07 (int BasicLineNo, int StatementNo, int Keyword, byte **Index);
+bool HandleClass08 (int BasicLineNo, int StatementNo, int Keyword, byte **Index);
+bool HandleClass09 (int BasicLineNo, int StatementNo, int Keyword, byte **Index);
+bool HandleClass10 (int BasicLineNo, int StatementNo, int Keyword, byte **Index);
+bool HandleClass11 (int BasicLineNo, int StatementNo, int Keyword, byte **Index);
+bool HandleClass12 (int BasicLineNo, int StatementNo, int Keyword, byte **Index);
+bool HandleClass13 (int BasicLineNo, int StatementNo, int Keyword, byte **Index);
+bool HandleClass14 (int BasicLineNo, int StatementNo, int Keyword, byte **Index);
+bool HandleClass15 (int BasicLineNo, int StatementNo, int Keyword, byte **Index);
+bool CheckSyntax (int BasicLineNo, byte *Line);
+
+/**********************************************************************************************************************************/
+/* Start of the program */
+/**********************************************************************************************************************************/
+
+int GetLineNumber (char **FirstAfter)
+
+/**********************************************************************************************************************************/
+/* Pre : The line must have been prepared into (global) `ConvertedSpectrumLine'. */
+/* Post : The BASIC line number has been returned, or -1 if there was none. */
+/* Import: None. */
+/**********************************************************************************************************************************/
+
+{
+ int LineNo = 0;
+ char *LineIndex;
+ bool SkipSpaces = TRUE;
+ bool Continue = TRUE;
+
+ LineIndex = ConvertedSpectrumLine;
+ while (*LineIndex && Continue)
+ if (*LineIndex == ' ') /* Skip leading spaces */
+ {
+ if (SkipSpaces)
+ LineIndex ++;
+ else
+ Continue = FALSE;
+ }
+ else if (isdigit (*LineIndex)) /* Process number */
+ {
+ LineNo = LineNo * 10 + *(LineIndex ++) - '0';
+ SkipSpaces = FALSE;
+ }
+ else
+ Continue = FALSE;
+ *FirstAfter = LineIndex;
+ if (SkipSpaces) /* Nothing found yet ? */
+ return (-1);
+ else
+ while ((**FirstAfter) == ' ') /* Skip trailing spaces */
+ (*FirstAfter) ++;
+ return (LineNo);
+}
+
+int MatchToken (int BasicLineNo, bool WantKeyword, char **LineIndex, byte *Token)
+
+/**********************************************************************************************************************************/
+/* Pre : `WantKeyword' is TRUE if we need in keyword match, `LineIndex' holds the position to match. */
+/* Post : If there was a match, the token value is returned in `Token' and `LineIndex' is pointing after the string plus any */
+/* any trailing space. */
+/* 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. */
+/* Import: None. */
+/**********************************************************************************************************************************/
+
+{
+ int Cnt;
+ size_t Length;
+ size_t LongestMatch = 0;
+ bool Match = FALSE;
+ bool Match2;
+
+ if ((**LineIndex) == ':') /* Special exception */
+ {
+ LongestMatch = 1;
+ Match = TRUE;
+ *Token = ':';
+ }
+ else for (Cnt = 0xA3 ; Cnt <= 0xFF ; Cnt ++) /* (Keywords start after the UDGs) */
+ {
+ Length = strlen (TokenMap[Cnt].Token);
+ if (CaseIndependant)
+ Match2 = !x_strnicmp (*LineIndex, TokenMap[Cnt].Token, Length);
+ else
+ Match2 = !strncmp (*LineIndex, TokenMap[Cnt].Token, Length);
+ if (Match2)
+ if (Length > LongestMatch)
+ {
+ LongestMatch = Length;
+ Match = TRUE;
+ *Token = Cnt;
+ }
+ }
+ if (!Match)
+ return (0); /* Signal: no match */
+ if (isalpha (*(*LineIndex + LongestMatch - 1)) && isalpha (*(*LineIndex + LongestMatch))) /* Continueing alpha string ? */
+ return (0); /* Then there's no match after all! (eg. 'INT' must not match 'INTER') */
+ *LineIndex += LongestMatch; /* Go past the token */
+ while ((**LineIndex) == ' ') /* Skip trailing spaces */
+ (*LineIndex) ++;
+ if (*Token == 0xA3 || *Token == 0xA4) /* 'SPECTRUM' or 'PLAY' ? */
+ switch (Is48KProgram) /* Then the program must be 128K */
+ {
+ case -1 : Is48KProgram = 0; break; /* Set the flag */
+ case 1 : fprintf (ErrStream, "ERROR - Line %d contains a 128K keyword, but the program\n"
+ "also uses UDGs \'T\' and/or \'U\'\n", BasicLineNo);
+ return (-2);
+ case 0 : break;
+ }
+ if ((WantKeyword && TokenMap[*Token].TokenType == 0) || /* Wanted keyword but got something else */
+ (!WantKeyword && TokenMap[*Token].TokenType == 1)) /* Did not want a keyword but got one nonetheless */
+ return (-1); /* Signal: match, but of wrong type */
+ else
+ return (1); /* Signal: match! */
+}
+
+int HandleNumbers (int BasicLineNo, char **BasicLine, byte **SpectrumLine)
+
+/**********************************************************************************************************************************/
+/* Pre : `BasicLineNo' holds the current BASIC line number, `BasicLine' points into the line, `SpectrumLine' points to the */
+/* TAPped Spectrum line. */
+/* Post : If there was a (floating point) number at this position, it has been processed into `SpectrumLine' and `LineIndex' is */
+/* pointing after the number. */
+/* The return value is: 0 = no number, 1 = number done, -1 = number error (already reported). */
+/* Import: None. */
+/**********************************************************************************************************************************/
+
+{
+#define SHIFT31BITS (double)2147483648.0 /* (= 2^31) */
+ char *StartOfNumber;
+ double Value = 0.0;
+ double Divider = 1.0;
+ double Exp = 0.0;
+ int IntValue;
+ byte Sign = 0x00;
+ unsigned long Mantissa;
+
+ if (!isdigit (**BasicLine) && /* Current character is not a digit ? */
+ (**BasicLine) != '.') /* And not a decimal point (eg. '.5') ? */
+ return (0); /* Then it can hardly be a number */
+ StartOfNumber = *BasicLine;
+ while (isdigit (**BasicLine)) /* First read the integer part */
+ Value = Value * 10 + *((*BasicLine) ++) - '0';
+ if ((**BasicLine) == '.') /* Decimal point ? */
+ { /* Read the decimal part */
+ (*BasicLine) ++;
+ while (isdigit (**BasicLine))
+ Value = Value + (Divider /= 10) * (*((*BasicLine) ++) - '0');
+ }
+ if ((**BasicLine) == 'e' || (**BasicLine) == 'E') /* Exponent ? */
+ {
+ (*BasicLine) ++;
+ if ((**BasicLine) == '+') /* Both "Ex" and "E+x" do the same thing */
+ (*BasicLine) ++;
+ else if ((**BasicLine) == '-') /* Negative exponent */
+ {
+ Sign = 0xFF;
+ (*BasicLine) ++;
+ }
+ while (isdigit (**BasicLine)) /* Read the exponent value */
+ Exp = Exp * 10 + *((*BasicLine) ++) - '0';
+ if (Sign == 0x00) /* Raise the resulting value to the read exponent */
+ Value = Value * pow (10.0, Exp);
+ else
+ Value = Value / pow (10.0, Exp);
+ }
+ strncpy ((char *)*SpectrumLine, StartOfNumber, *BasicLine - StartOfNumber); /* Insert the ASCII value first */
+ (*SpectrumLine) += (*BasicLine - StartOfNumber);
+ IntValue = (int)floor (Value);
+ if (Value == IntValue && Value >= -65536 && Value < 65536) /* Small integer ? */
+ {
+ *((*SpectrumLine) ++) = 0x0E; /* Insert number marker */
+ *((*SpectrumLine) ++) = 0x00;
+ if (IntValue >= 0) /* Insert sign */
+ *((*SpectrumLine) ++) = 0x00;
+ else
+ {
+ *((*SpectrumLine) ++) = 0xFF;
+ IntValue += 65536; /* Maintain bug in Spectrum ROM - INT(-65536) will result in -1 */
+ }
+ *((*SpectrumLine) ++) = (byte)(IntValue & 0xFF);
+ *((*SpectrumLine) ++) = (byte)(IntValue >> 8);
+ *((*SpectrumLine) ++) = 0x00;
+ }
+ else /* Need to store in full floating point format */
+ {
+ if (Value < 0)
+ {
+ Sign = 0x80; /* Sign bit is high bit of byte 2 */
+ Value = -Value;
+ }
+ else
+ Sign = 0x00;
+ Exp = floor (x_log2 (Value));
+ if (Exp < -129 || Exp > 126)
+ {
+ fprintf (ErrStream, "ERROR - Number too big in line %d\n", BasicLineNo);
+ return (-1);
+ }
+ Mantissa = (unsigned long)floor ((Value / pow (2.0, Exp) - 1.0) * SHIFT31BITS + 0.5); /* Calculate mantissa */
+ *((*SpectrumLine) ++) = 0x0E; /* Insert number marker */
+ *((*SpectrumLine) ++) = (byte)Exp + 0x81; /* Insert exponent */
+ *((*SpectrumLine) ++) = (byte)((Mantissa >> 24) & 0x7F) | Sign; /* Insert mantissa */
+ *((*SpectrumLine) ++) = (byte)((Mantissa >> 16) & 0xFF); /* (Big endian!) */
+ *((*SpectrumLine) ++) = (byte)((Mantissa >> 8) & 0xFF);
+ *((*SpectrumLine) ++) = (byte)(Mantissa & 0xFF);
+ }
+ return (1);
+}
+
+int ExpandSequences (int BasicLineNo, char **BasicLine, byte **SpectrumLine, bool StripSpaces)
+
+/**********************************************************************************************************************************/
+/* Pre : `BasicLineNo' holds the current BASIC line number, `BasicLine' points into the line, `SpectrumLine' points to the */
+/* TAPped Spectrum line. */
+/* Post : If there was an expandable '{...}' sequence at this position, it has been processed into `SpectrumLine', `LineIndex' */
+/* is pointing after the sequence. Returned is -1 for error, 0 for no expansion, 1 for expansion. */
+/* Import: None. */
+/**********************************************************************************************************************************/
+
+{
+ char *StartOfSequence;
+ byte Attribute = 0;
+ byte AttributeLength = 0;
+ byte AttributeVal1 = 0;
+ byte AttributeVal2 = 0;
+ byte OldCharacter;
+ int Cnt;
+
+ if (**BasicLine != '{')
+ return (0);
+ StartOfSequence = (*BasicLine) + 1;
+ /* 'CODE' and 'CAT' were added for the sole purpuse of allowing them to be OPEN #'ed as channels! */
+ if (!x_strnicmp (StartOfSequence, "CODE}", 5)) /* Special: 'CODE' */
+ {
+ *((*SpectrumLine) ++) = 0xAF;
+ (*BasicLine) += 6;
+ return (1);
+ }
+ if (!x_strnicmp (StartOfSequence, "CAT}", 4)) /* Special: 'CAT' */
+ {
+ *((*SpectrumLine) ++) = 0xCF;
+ (*BasicLine) += 5;
+ return (1);
+ }
+ if (!x_strnicmp (StartOfSequence, "(C)}", 4))
+ { /* Form "{(C)}" -> copyright sign */
+ *((*SpectrumLine) ++) = 0x7F;
+ (*BasicLine) += 5;
+ if (StripSpaces)
+ while ((**BasicLine) == ' ') /* Skip trailing spaces */
+ (*BasicLine) ++;
+ return (1);
+ }
+ if (*StartOfSequence == '+' && *(StartOfSequence + 1) >= '1' && *(StartOfSequence + 1) <= '8' && *(StartOfSequence + 2) == '}')
+ { /* Form "{+X}" -> block graphics with shift */
+ *((*SpectrumLine) ++) = 0x88 + (((*(StartOfSequence + 1) - '0') % 8) ^ 7);
+ (*BasicLine) += 4;
+ if (StripSpaces)
+ while ((**BasicLine) == ' ')
+ (*BasicLine) ++;
+ return (1);
+ }
+ if (*StartOfSequence == '-' && *(StartOfSequence + 1) >= '1' && *(StartOfSequence + 1) <= '8' && *(StartOfSequence + 2) == '}')
+ { /* Form "{-X}" -> block graphics without shift */
+ *((*SpectrumLine) ++) = 0x80 + (*(StartOfSequence + 1) - '0') % 8;
+ (*BasicLine) += 4;
+ if (StripSpaces)
+ while ((**BasicLine) == ' ')
+ (*BasicLine) ++;
+ return (1);
+ }
+ if (toupper (*StartOfSequence) >= 'A' && toupper (*StartOfSequence) <= 'U' && *(StartOfSequence + 1) == '}')
+ { /* Form "{X}" -> UDG */
+ if (toupper (*StartOfSequence) == 'T' || toupper (*StartOfSequence) == 'U') /* 'T' or 'U' ? */
+ switch (Is48KProgram) /* Then the program must be 48K */
+ {
+ case -1 : Is48KProgram = 1; break; /* Set the flag */
+ case 0 : fprintf (ErrStream, "ERROR - Line %d contains UDGs \'T\' and/or \'U\'\n"
+ "but the program was already marked 128K\n", BasicLineNo);
+ return (-1);
+ case 1 : break;
+ }
+ *((*SpectrumLine) ++) = 0x90 + toupper (*StartOfSequence) - 'A';
+ (*BasicLine) += 3;
+ if (StripSpaces)
+ while ((**BasicLine) == ' ')
+ (*BasicLine) ++;
+ return (1);
+ }
+ if (isxdigit (*StartOfSequence) && isxdigit (*(StartOfSequence + 1)) && *(StartOfSequence + 2) == '}')
+ { /* Form "{XX}" -> below 32 */
+ if (*StartOfSequence <= '9')
+ (**SpectrumLine) = *StartOfSequence - '0';
+ else
+ (**SpectrumLine) = toupper (*StartOfSequence) - 'A' + 10;
+ if (*(StartOfSequence + 1) <= '9')
+ (**SpectrumLine) = (**SpectrumLine) * 16 + *(StartOfSequence + 1) - '0';
+ else
+ (**SpectrumLine) = (**SpectrumLine) * 16 + toupper (*(StartOfSequence + 1)) - 'A' + 10;
+ (*SpectrumLine) ++;
+ (*BasicLine) += 4;
+ if (StripSpaces)
+ while ((**BasicLine) == ' ')
+ (*BasicLine) ++;
+ return (1);
+ }
+ if (!x_strnicmp (StartOfSequence, "INK", 3))
+ {
+ Attribute = 0x10;
+ AttributeLength = 3;
+ }
+ else if (!x_strnicmp (StartOfSequence, "PAPER", 5))
+ {
+ Attribute = 0x11;
+ AttributeLength = 5;
+ }
+ else if (!x_strnicmp (StartOfSequence, "FLASH", 5))
+ {
+ Attribute = 0x12;
+ AttributeLength = 5;
+ }
+ else if (!x_strnicmp (StartOfSequence, "BRIGHT", 6))
+ {
+ Attribute = 0x13;
+ AttributeLength = 6;
+ }
+ else if (!x_strnicmp (StartOfSequence, "INVERSE", 7))
+ {
+ Attribute = 0x14;
+ AttributeLength = 7;
+ }
+ else if (!x_strnicmp (StartOfSequence, "OVER", 4))
+ {
+ Attribute = 0x15;
+ AttributeLength = 4;
+ }
+ else if (!x_strnicmp (StartOfSequence, "AT", 2))
+ {
+ Attribute = 0x16;
+ AttributeLength = 2;
+ }
+ else if (!x_strnicmp (StartOfSequence, "TAB", 3))
+ {
+ Attribute = 0x17;
+ AttributeLength = 3;
+ }
+ if (Attribute > 0)
+ {
+ StartOfSequence += AttributeLength;
+ while (*StartOfSequence == ' ')
+ StartOfSequence ++;
+ while (isdigit (*StartOfSequence))
+ AttributeVal1 = AttributeVal1 * 10 + *(StartOfSequence ++) - '0';
+ if (Attribute == 0x16 || Attribute == 0x17)
+ {
+ if (*StartOfSequence != ',')
+ Attribute = 0;
+ else
+ {
+ StartOfSequence ++; /* (Step past the comma) */
+ while (*StartOfSequence == ' ')
+ StartOfSequence ++;
+ while (isdigit (*StartOfSequence))
+ AttributeVal2 = AttributeVal2 * 10 + *(StartOfSequence ++) - '0';
+ }
+ }
+ if (*StartOfSequence != '}') /* Need closing bracket */
+ Attribute = 0;
+ if (Attribute > 0)
+ {
+ *((*SpectrumLine) ++) = Attribute;
+ *((*SpectrumLine) ++) = AttributeVal1;
+ if (Attribute == 0x16 || Attribute == 0x17)
+ *((*SpectrumLine) ++) = AttributeVal2;
+ (*BasicLine) = StartOfSequence + 1;
+ if (StripSpaces)
+ while ((**BasicLine) == ' ')
+ (*BasicLine) ++;
+ return (1);
+ }
+ }
+ if (!NoWarnings)
+ {
+ for (Cnt = 0 ; *((*BasicLine) + Cnt) && *((*BasicLine) + Cnt) != '}' ; Cnt ++)
+ ;
+ if (*((*BasicLine) + Cnt) == '}')
+ {
+ OldCharacter = *((*BasicLine) + Cnt + 1);
+ *((*BasicLine) + Cnt + 1) = '\0';
+ printf ("WARNING - Unexpandable sequence \"%s\" in line %d\n", (*BasicLine), BasicLineNo);
+ *((*BasicLine) + Cnt + 1) = OldCharacter;
+ return (0);
+ }
+ }
+ return (0);
+}
+
+int PrepareLine (char *LineIn, int FileLineNo, char **FirstToken)
+
+/**********************************************************************************************************************************/
+/* Pre : `LineIn' points to the read line, `FileLineNo' holds the real line number. */
+/* Post : Multiple spaces have been removed (unless within a string), the BASIC line number has been found and `FirstToken' is */
+/* pointing at the first non-whitespace character after the line number. */
+/* Bad characters are reported, as well as any other error. The return value is the BASIC line number, -1 if error, or */
+/* -2 if the (empty!) line should be skipped. */
+/* Import: GetLineNumber. */
+/**********************************************************************************************************************************/
+
+{
+ char *IndexIn;
+ char *IndexOut;
+ bool InString = FALSE;
+ bool SingleSeparator = FALSE;
+ bool StillOk = TRUE;
+ bool DoingREM = FALSE;
+ int BasicLineNo = -1;
+ static int PreviousBasicLineNo = -1;
+
+ IndexIn = LineIn;
+ IndexOut = ConvertedSpectrumLine;
+ while (*IndexIn && StillOk)
+ {
+ if (*IndexIn == '\t') /* EXCEPTION: Print ' */
+ {
+ *(IndexOut ++) = 0x06;
+ IndexIn ++;
+ }
+ else if (*IndexIn < 32 || *IndexIn >= 127) /* (Exclude copyright sign as well) */
+ StillOk = FALSE;
+ else
+ {
+ if (!DoingREM)
+ if (!x_strnicmp (IndexIn, " REM ", 5) || /* Going through REM statement ? */
+ !x_strnicmp (IndexIn, ":REM ", 5))
+ DoingREM = TRUE; /* Signal: copy anything and everything ASCII */
+ if (InString || DoingREM)
+ *(IndexOut ++) = *IndexIn;
+ else
+ {
+ if (*IndexIn == ' ')
+ {
+ if (!SingleSeparator) /* Remove multiple spaces */
+ {
+ SingleSeparator = TRUE;
+ *(IndexOut ++) = *IndexIn;
+ }
+ }
+ else
+ {
+ SingleSeparator = FALSE;
+ *(IndexOut ++) = *IndexIn;
+ }
+ }
+ if (*IndexIn == '\"' && !DoingREM)
+ InString = !InString;
+ IndexIn ++;
+ }
+ }
+ *IndexOut = '\0';
+ if (!StillOk)
+ if (*IndexIn == 0x0D || *IndexIn == 0x0A) /* 'Correct' for end-of-line */
+ StillOk = TRUE; /* (Accept CR and/or LF as end-of-line) */
+ BasicLineNo = GetLineNumber (FirstToken);
+ if (InString)
+ fprintf (ErrStream, "ERROR - %s line %d misses terminating quote\n",
+ BasicLineNo < 0 ? "ASCII" : "BASIC", BasicLineNo < 0 ? FileLineNo : BasicLineNo);
+ else if (!StillOk)
+ fprintf (ErrStream, "ERROR - %s line %d contains a bad character (code %02Xh)\n",
+ BasicLineNo < 0 ? "ASCII" : "BASIC", BasicLineNo < 0 ? FileLineNo : BasicLineNo, *IndexIn);
+ else if (BasicLineNo < 0) /* Could not read line number */
+ {
+ if (!(**FirstToken)) /* Line is completely empty ? */
+ {
+ if (!NoWarnings)
+ printf ("WARNING - Skipping empty ASCII line %d\n", FileLineNo);
+ return (-2); /* Signal: skip entire line */
+ }
+ else
+ {
+ fprintf (ErrStream, "ERROR - Missing line number in ASCII line %d\n", FileLineNo);
+ StillOk = FALSE;
+ }
+ }
+ else if (PreviousBasicLineNo >= 0) /* Not the first line ? */
+ {
+ if (BasicLineNo < PreviousBasicLineNo) /* This line number smaller than previous ? */
+ {
+ fprintf (ErrStream, "ERROR - Line number %d is smaller than previous line number %d\n", BasicLineNo, PreviousBasicLineNo);
+ StillOk = FALSE;
+ }
+ else if (BasicLineNo == PreviousBasicLineNo && !NoWarnings) /* Same line number as previous ? */
+ printf ("WARNING - Duplicate use of line number %d\n", BasicLineNo); /* (BASIC can handle it after all...) */
+ }
+ else if (!(**FirstToken)) /* Line contains only a line number ? */
+ {
+ fprintf (ErrStream, "ERROR - Line %d contains no statements!\n", BasicLineNo);
+ StillOk = FALSE;
+ }
+ PreviousBasicLineNo = BasicLineNo; /* Remember this line number */
+ if (!InString && StillOk)
+ return (BasicLineNo);
+ else
+ return (-1);
+}
+
+bool CheckEnd (int BasicLineNo, int StatementNo, byte **Index)
+
+/**********************************************************************************************************************************/
+/* Pre : `BasicLineNo' holds the line number, `StatementNo' the statement number, `Index' the current position in the line. */
+/* Post : A check is made whether the end of the current statement has been reached. */
+/* If so, an error is reported and TRUE is returned (so FALSE indicates that everything is still fine and dandy). */
+/* Import: none. */
+/**********************************************************************************************************************************/
+
+{
+ if (**Index == ':' || **Index == 0x0D) /* End of statement or end of line ? */
+ {
+ fprintf (ErrStream, "ERROR in line %d, statement %d - Unexpected end of statement\n", BasicLineNo, StatementNo);
+ return (TRUE);
+ }
+ return (FALSE);
+}
+
+bool ScanVariable (int BasicLineNo, int StatementNo, int Keyword, byte **Index, bool *Type, int *NameLen, int AllowSlicing)
+
+/**********************************************************************************************************************************/
+/* Pre : `BasicLineNo' holds the line number, `StatementNo' the statement number, `Keyword' the keyword to which this operand */
+/* belongs, `Index' the current position in the line. */
+/* `AllocSlicing' is one of the following values: */
+/* -1 = Don't check for slicing/indexing (used by DEF FN) */
+/* 0 = No slicing/indexing allowed */
+/* 1 = Either slicing or indexing may follow (indices being numeric) */
+/* 2 = Only numeric indexing may follow (used by LET and READ) */
+/* Post : A check has been made whether there's a variable at the current position. If so, it has been skipped. */
+/* Slicing is handled here as well, but notice that this is not necessarily correct! */
+/* Single letter string variables can be either flat or array and both possibilities are considered here. */
+/* Both "a$(1 TO 10)" and "a$(1, 2)" are correct to BASIC, but depend on whether a "DIM" statement was used. */
+/* The length of the found string (without any '$') is returned in `NameLen', its type is returned in `Type' (TRUE for */
+/* numeric and FALSE for string variables). The return value is TRUE is all went well. Errors have already been reported. */
+/* The return value is FALSE either when no variable is at this point or an error was found. */
+/* `NameLen' is returned 0 if no variable was detected here, or > 0 if in error. */
+/* Import: ScanExpression. */
+/**********************************************************************************************************************************/
+
+{
+ bool SubType;
+ bool IsArray = FALSE;
+ bool SetTokenBracket = FALSE;
+
+ Keyword = Keyword; /* (Keep compilers happy) */
+ *Type = TRUE; /* Assume it will be numeric */
+ *NameLen = 0;
+ if (!isalpha (**Index)) /* The first character must be alphabetic for a variable */
+ return (FALSE);
+ *NameLen = 1;
+ while (isalnum (*(++ (*Index)))) /* Read on, until end of the word */
+ (*NameLen) ++;
+ if (**Index == '$') /* It's a string variable ? */
+ {
+ if (*NameLen > 1) /* String variables can only have a single character name */
+ {
+ fprintf (ErrStream, "ERROR in line %d, statement %d - String variables can only have single character names\n",
+ BasicLineNo, StatementNo);
+ return (FALSE);
+ }
+ (*Index) ++;
+ *Type = FALSE;
+ }
+#ifdef __DEBUG__
+ printf ("DEBUG - %sScanVariable, Type is %s\n", ListSpaces, *Type ? "NUM" : "ALPHA");
+#endif
+ if (AllowSlicing >= 0 && **Index == '(') /* Slice the string ? */
+ {
+#ifdef __DEBUG__
+ printf ("DEBUG - %sScanVariable, reading index\n", ListSpaces);
+#endif
+ if (*NameLen > 1) /* Arrays can only have a single character name */
+ {
+ fprintf (ErrStream, "ERROR in line %d, statement %d - Arrays can only have single character names\n",
+ BasicLineNo, StatementNo);
+ return (FALSE);
+ }
+ if (AllowSlicing == 0) /* Slicing/Indexing not allowed ? */
+ {
+ fprintf (ErrStream, "ERROR in line %d, statement %d - Slicing/Indexing not allowed\n", BasicLineNo, StatementNo);
+ return (FALSE);
+ }
+ (*Index) ++; /* (Skip the bracket) */
+ if (**Index == ')') /* Empty slice "a$()" is not ok */
+ {
+ fprintf (ErrStream, "ERROR in line %d, statement %d - Empty array index not allowed\n", BasicLineNo, StatementNo);
+ return (FALSE);
+ }
+ if (**Index == 0xCC) /* "a$( TO num)" or "a$( TO )" */
+ {
+ if (AllowSlicing == 2)
+ {
+ fprintf (ErrStream, "ERROR in line %d, statement %d - Slicing token \"TO\" inappropriate for arrays\n",
+ BasicLineNo, StatementNo);
+ return (FALSE);
+ }
+ }
+ else /* Not "a$( TO num)" nor "a$( TO )" */
+ {
+ if (!TokenBracket)
+ {
+ TokenBracket = TRUE; /* Allow complex expression */
+ SetTokenBracket = TRUE;
+ }
+ if (!ScanExpression (BasicLineNo, StatementNo, '(', Index, &SubType, 0)) /* First parameter */
+ return (FALSE);
+ if (SetTokenBracket)
+ TokenBracket = FALSE;
+ if (!SubType) /* Must be numeric */
+ {
+ fprintf (ErrStream, "ERROR in line %d, statement %d - Variables indices must be numeric\n", BasicLineNo, StatementNo);
+ return (FALSE);
+ }
+ if (**Index == ')') /* "a$(num)" is ok */
+ {
+ (*Index) ++;
+#ifdef __DEBUG__
+ printf ("DEBUG - %sScanVariable, index ending, next char is \"%s\"\n", ListSpaces, TokenMap[**Index].Token);
+#endif
+ return (TRUE);
+ }
+ }
+ if (**Index != 0xCC && **Index != ',') /* Either an array or a slice */
+ {
+ fprintf (ErrStream, "ERROR in line %d, statement %d - Unexpected index character \"%c\"\n",
+ BasicLineNo, StatementNo, **Index);
+ return (FALSE);
+ }
+ if (**Index == ',')
+ IsArray = TRUE;
+ else
+ {
+ if (AllowSlicing == 2)
+ {
+ fprintf (ErrStream, "ERROR in line %d, statement %d - Slicing token \"TO\" inappropriate for arrays\n",
+ BasicLineNo, StatementNo);
+ return (FALSE);
+ }
+ if (*Type) /* Only character strings can be sliced */
+ {
+ fprintf (ErrStream, "ERROR in line %d, statement %d - Only character strings can be sliced\n", BasicLineNo, StatementNo);
+ return (FALSE);
+ }
+ }
+ do
+ {
+ (*Index) ++; /* Skip each "," (or the "TO" for non-arrays) */
+ if (!TokenBracket)
+ {
+ TokenBracket = TRUE;
+ SetTokenBracket = TRUE;
+ }
+ if (!ScanExpression (BasicLineNo, StatementNo, '(', Index, &SubType, 0)) /* Second or further parameter */
+ return (FALSE);
+ if (SetTokenBracket)
+ TokenBracket = FALSE;
+ if (!SubType) /* Must be numeric */
+ {
+ fprintf (ErrStream, "ERROR in line %d, statement %d - Variables indices must be numeric\n", BasicLineNo, StatementNo);
+ return (FALSE);
+ }
+ if (!IsArray && **Index != ')')
+ {
+ BADTOKEN ("\")\"", TokenMap[**Index].Token);
+ return (FALSE);
+ }
+ else if (IsArray && **Index != ',' && **Index != ')')
+ {
+ BADTOKEN ("\",\"", TokenMap[**Index].Token);
+ return (FALSE);
+ }
+ }
+ while (**Index != ')');
+ (*Index) ++; /* (Step past closing bracket) */
+#ifdef __DEBUG__
+ printf ("DEBUG - %sScanVariable, index ending, next char is \"%s\"\n", ListSpaces, TokenMap[**Index].Token);
+#endif
+ }
+ return (TRUE);
+}
+
+bool SliceDirectString (int BasicLineNo, int StatementNo, int Keyword, byte **Index)
+
+/**********************************************************************************************************************************/
+/* Pre : `BasicLineNo' holds the line number, `StatementNo' the statement number, `Keyword' the keyword to which this operand */
+/* belongs, `Index' the current position in the line. */
+/* A direct string has just been read and a '(' character is currently under the cursor. */
+/* Post : Slicing is handled here. */
+/* Possible are "string"(), "string"(num), "string"( TO ), "string"(num TO ), "string"( TO num) and "string"(num TO num). */
+/* The return value is FALSE if an error was found (which has already been reported here). */
+/* Import: ScanExpression. */
+/**********************************************************************************************************************************/
+
+{
+ bool SubType;
+ bool SetTokenBracket = FALSE;
+
+ Keyword = Keyword; /* (Keep compilers happy) */
+ (*Index) ++; /* Step past the opening bracket */
+ if (**Index == ')') /* Empty slice "abc"() is ok */
+ {
+ (*Index) ++;
+ return (TRUE);
+ }
+ if (**Index != 0xCC) /* Not "abc"( TO num) nor "abc"( TO ) */
+ {
+ if (!TokenBracket)
+ {
+ TokenBracket = TRUE;
+ SetTokenBracket = TRUE;
+ }
+ if (!ScanExpression (BasicLineNo, StatementNo, '(', Index, &SubType, 0)) /* First parameter */
+ return (FALSE);
+ if (SetTokenBracket)
+ TokenBracket = FALSE;
+ if (!SubType) /* Must be numeric */
+ {
+ fprintf (ErrStream, "ERROR in line %d, statement %d - Slice values must be numeric\n", BasicLineNo, StatementNo);
+ return (FALSE);
+ }
+ }
+ if (**Index == ')') /* "abc"(num) is ok */
+ {
+ (*Index) ++;
+ return (TRUE);
+ }
+ if (**Index != 0xCC) /* ('TO') */
+ {
+ fprintf (ErrStream, "ERROR in line %d, statement %d - Unexpected index character\n", BasicLineNo, StatementNo);
+ return (FALSE);
+ }
+ (*Index) ++;
+ if (**Index == ')') /* "abc"(num TO ) is ok */
+ {
+ (*Index) ++;
+ return (TRUE);
+ }
+ if (!TokenBracket)
+ {
+ TokenBracket = TRUE;
+ SetTokenBracket = TRUE;
+ }
+ if (!ScanExpression (BasicLineNo, StatementNo, '(', Index, &SubType, 0)) /* Second parameter */
+ return (FALSE);
+ if (SetTokenBracket)
+ TokenBracket = FALSE;
+ if (!SubType) /* Must be numeric */
+ {
+ fprintf (ErrStream, "ERROR in line %d, statement %d - Slice values must be numeric\n", BasicLineNo, StatementNo);
+ return (FALSE);
+ }
+ if (**Index != ')')
+ {
+ BADTOKEN ("\")\"", TokenMap[**Index].Token);
+ return (FALSE);
+ }
+ (*Index) ++; /* (Step past closing bracket) */
+ return (TRUE);
+}
+
+bool ScanStream (int BasicLineNo, int StatementNo, int Keyword, byte **Index)
+
+/**********************************************************************************************************************************/
+/* Pre : `BasicLineNo' holds the line number, `StatementNo' the statement number, `Keyword' the keyword to which this operand */
+/* belongs, `Index' the current position in the line. */
+/* A stream hash mark (`#') has just been read. */
+/* Post : The following stream number is checked to be a numeric expression. */
+/* The return value is FALSE if an error was found (which has already been reported here). */
+/* Import: HandleClass06. */
+/**********************************************************************************************************************************/
+
+{
+ if (!SignalInterface1 (BasicLineNo, StatementNo, 0))
+ return (FALSE);
+ return (HandleClass06 (BasicLineNo, StatementNo, Keyword, Index)); /* Find numeric expression */
+}
+
+bool SignalInterface1 (int BasicLineNo, int StatementNo, int NewMode)
+
+/**********************************************************************************************************************************/
+/* Pre : `BasicLineNo' holds the line number, `StatementNo' the statement number, `NewMode' holds the required hardware mode. */
+/* Post : The required hardware is tested for conflicts. */
+/* The return value is FALSE if there was a conflict (which has already been reported here). */
+/* Import: none. */
+/**********************************************************************************************************************************/
+
+{
+ if ((NewMode == 1 && UsesInterface1 == 2) || /* Interface1 required, but already flagged Opus ? */
+ (NewMode == 2 && UsesInterface1 == 1)) /* Opus required, but already flagged Interface1 ? */
+ {
+ fprintf (ErrStream, "ERROR in line %d, statement %d - The program uses commands that are specific\n"
+ "for Interface 1 and Opus Discovery, but don't exist on both devices\n",
+ BasicLineNo, StatementNo);
+ return (FALSE);
+ }
+ UsesInterface1 = NewMode;
+ return (TRUE);
+}
+
+bool ScanChannel (int BasicLineNo, int StatementNo, int Keyword, byte **Index, byte *WhichChannel)
+
+/**********************************************************************************************************************************/
+/* Pre : `BasicLineNo' holds the line number, `StatementNo' the statement number, `Keyword' the keyword to which this operand */
+/* belongs, `Index' the current position in the line. */
+/* Post : A channel identifier of the form "x";n; must follow. `x' is a single alphanumeric character, `n' is a numeric */
+/* expression, the rest are required characters. */
+/* The found channel identifier ('x') is returned (in lowercase) in `WhichChannel'. */
+/* The return value is FALSE if an error was found (which has already been reported here). */
+/* Import: HandleClass06, CheckEnd. */
+/**********************************************************************************************************************************/
+
+{
+ int NeededHardware = 0; /* (Default to Interface 1) */
+
+ *WhichChannel = '\0';
+ if (CheckEnd (BasicLineNo, StatementNo, Index))
+ return (FALSE);
+ if (**Index != '\"')
+ {
+ if (!HandleClass06 (BasicLineNo, StatementNo, Keyword, Index))/* EXCEPTION: The Opus allows '<num>' to abbreviate '"m";<num>' */
+ {
+ fprintf (ErrStream, "Expected to find a channel identifier\n");
+ return (FALSE);
+ }
+ *WhichChannel = 'm';
+ if (!SignalInterface1 (BasicLineNo, StatementNo, 2)) /* Signal the Opus specificness */
+ return (FALSE);
+ if (CheckEnd (BasicLineNo, StatementNo, Index))
+ return (FALSE);
+ if (**Index != ';')
+ {
+ BADTOKEN ("\";\"", TokenMap[**Index].Token);
+ return (FALSE);
+ }
+ (*Index) ++;
+ if (CheckEnd (BasicLineNo, StatementNo, Index))
+ return (FALSE);
+ }
+ else
+ {
+ (*Index) ++;
+ if (CheckEnd (BasicLineNo, StatementNo, Index))
+ return (FALSE);
+ if (!isalpha (**Index) && /* (Ordinary channel) */
+ **Index != '#' && /* (Linked channel, OPEN # only) */
+ **Index != 0xAF && /* ('CODE' channel, OPEN # only) */
+ **Index != 0xCF) /* ('CAT' channel, OPEN # only) */
+ {
+ fprintf (ErrStream, "ERROR in line %d, statement %d - Channel name must be alphanumeric\n", BasicLineNo, StatementNo);
+ return (FALSE);
+ }
+ *WhichChannel = tolower (**Index);
+ (*Index) ++;
+ if (CheckEnd (BasicLineNo, StatementNo, Index))
+ return (FALSE);
+ if (**Index != '\"')
+ {
+ fprintf (ErrStream, "ERROR in line %d, statement %d - Channel name must be single character\n", BasicLineNo, StatementNo);
+ return (FALSE);
+ }
+ (*Index) ++;
+ if (*WhichChannel == 'k' || *WhichChannel == 's' || *WhichChannel == 'p' || /* (Normal Spectrum channels) */
+ *WhichChannel == 'm' || *WhichChannel == 't' || *WhichChannel == 'b' ||
+ *WhichChannel == '#' || *WhichChannel == 0xCF) /* ('CAT' channel) */
+ NeededHardware = 0;
+ else if (*WhichChannel == 'n') /* Network channel is available on Interface 1 but not on Opus */
+ NeededHardware = 1;
+ else if (*WhichChannel == 'j' || /* (Opus: Joystick channel) */
+ *WhichChannel == 'd' || /* (Opus: disk channel) */
+ *WhichChannel == 0xAF) /* (Opus: 'CODE' channel) */
+ NeededHardware = 2;
+ if (!SignalInterface1 (BasicLineNo, StatementNo, NeededHardware))
+ return (FALSE);
+ if (*WhichChannel == 'm' || *WhichChannel == 'd' || *WhichChannel == 'n' || /* Continue checking with these channels only */
+ *WhichChannel == '#' || *WhichChannel == 0xCF)
+ {
+ if (CheckEnd (BasicLineNo, StatementNo, Index))
+ return (FALSE);
+ if (**Index != ';')
+ {
+ BADTOKEN ("\";\"", TokenMap[**Index].Token);
+ return (FALSE);
+ }
+ (*Index) ++;
+ if (!HandleClass06 (BasicLineNo, StatementNo, Keyword, Index)) /* Find numeric expression */
+ return (FALSE);
+ if (*WhichChannel == 'm') /* Omly the 'm' channel requires a ';' character following */
+ {
+ if (CheckEnd (BasicLineNo, StatementNo, Index))
+ return (FALSE);
+ if (**Index != ';')
+ {
+ BADTOKEN ("\";\"", TokenMap[**Index].Token);
+ return (FALSE);
+ }
+ (*Index) ++;
+ if (CheckEnd (BasicLineNo, StatementNo, Index))
+ return (FALSE);
+ }
+ }
+ }
+ return (TRUE);
+}
+
+bool ScanExpression (int BasicLineNo, int StatementNo, int Keyword, byte **Index, bool *Type, int Level)
+
+/**********************************************************************************************************************************/
+/* Pre : `BasicLineNo' holds the line number, `StatementNo' the statement number, `Keyword' the keyword to which this operand */
+/* belongs, `Index' the current position in the line. */
+/* `Level' is used for recursion and must be 0 when called, unless when cassed from ScanVariable (then it must be 1). */
+/* Post : An expression must be found, either numerical or string. Its type is returned in `Type' (TRUE for numerical). */
+/* All subexpressions, between brackets, are dealt with using recursion. */
+/* The return value is FALSE if an error was found (which has already been reported here). */
+/* Import: ScanExpression (recursive), SliceDirectString, ScanVariable, HandleClassXX. */
+/**********************************************************************************************************************************/
+
+{
+ bool More = TRUE;
+ bool SubType = TRUE; /* (Assume numeric expression) */
+ bool SubSubType;
+ bool TypeKnown = FALSE;
+ bool TotalTypeKnown = FALSE;
+ bool Dummy;
+ int VarNameLen;
+ int ClassIndex = -1;
+ byte ThisToken;
+
+#ifdef __DEBUG__
+ RecurseLevel ++;
+ memset (ListSpaces, ' ', RecurseLevel * 2);
+ ListSpaces[RecurseLevel * 2] = '\0';
+ printf ("DEBUG - %sEnter ScanExpression\n", ListSpaces);
+#endif
+ if (**Index == '+' || **Index == '-') /* Unary plus and minus */
+ {
+ *Type = TRUE; /* Then we expect a numeric expression */
+ TypeKnown = TRUE;
+ (*Index) ++; /* Skip the sign */
+ }
+ while (More)
+ {
+#ifdef __DEBUG__
+ printf ("DEBUG - %sScanExpression sub (keyword \"%s\"), first char is \"%s\"\n",
+ ListSpaces, TokenMap[Keyword].Token, TokenMap[**Index].Token);
+#endif
+ if (**Index == '(') /* Opening bracket ? */
+ {
+#ifdef __DEBUG__
+ printf ("DEBUG - %sRecurse ScanExpression for \"(\"\n", ListSpaces);
+#endif
+ (*Index) ++; /* The 'parent' steps past the opening bracket */
+ if (!ScanExpression (BasicLineNo, StatementNo, '(', Index, &SubSubType, Level + 1)) /* Recurse */
+ return (FALSE);
+ if (TypeKnown && SubSubType != SubType) /* Bad subexpression type ? */
+ {
+ fprintf (ErrStream, "ERROR in line %d, statement %d - Type conflict in expression\n", BasicLineNo, StatementNo);
+ return (FALSE);
+ }
+ else if (!TypeKnown) /* We didn't have an expected type yet ? */
+ {
+ SubType = SubSubType;
+ TypeKnown = TRUE;
+ }
+ (*Index) ++; /* The 'parent' steps past the closing bracket too */
+ if (**Index == '(') /* Slicing ? */
+ {
+ if (!SubSubType) /* Result was a string ? */
+ {
+ if (!SliceDirectString (BasicLineNo, StatementNo, Keyword, Index))
+ return (FALSE);
+ }
+ else /* No, it was numerical, which you can't slice */
+ {
+ fprintf (ErrStream, "ERROR in line %d, statement %d - cannot slice a numerical value\n",
+ BasicLineNo, StatementNo);
+ return (FALSE);
+ }
+ }
+ }
+ else if (**Index == ')') /* Closing bracket ? */
+ { /* Leave the bracket for the parent, to allow functions (eg. "ATTR (...)") */
+ if (!TotalTypeKnown) /* 'Simple' expression ? */
+ *Type = SubType; /* Set return type */
+#ifdef __DEBUG__
+ printf ("DEBUG - %sLeave ScanExpression, Type is %s next char is \"%s\"\n",
+ ListSpaces, *Type ? "NUM" : "ALPHA", TokenMap[**Index].Token);
+ if (-- RecurseLevel > 0)
+ memset (ListSpaces, ' ', RecurseLevel * 2);
+ ListSpaces[RecurseLevel * 2] = '\0';
+#endif
+ return (TRUE); /* Step out of the recursion */
+ }
+ else if (**Index == ':' || **Index == 0x0D) /* End of statement or end of line ? */
+ {
+ if (!TotalTypeKnown) /* 'Simple' expression ? */
+ *Type = SubType; /* Set return type */
+ if (Level) /* Not on lowest level ? */
+ {
+ fprintf (ErrStream, "ERROR in line %d, statement %d - too few closing brackets\n", BasicLineNo, StatementNo);
+ return (FALSE);
+ }
+ More = FALSE;
+ }
+ else if (isdigit (**Index) || **Index == '.') /* Number ? */
+ {
+ if (!TypeKnown) /* Unknown expression type yet ? */
+ {
+ TypeKnown = TRUE; /* Signal: it is numeric */
+ SubType = TRUE;
+ }
+ else if (!SubType) /* Type was known to be string ? */
+ {
+ fprintf (ErrStream, "ERROR in line %d, statement %d - Type conflict in expression\n", BasicLineNo, StatementNo);
+ return (FALSE);
+ }
+ while (*(++ (*Index)) != 0x0E) /* Skip until the number marker */
+ ;
+ (*Index) ++;
+ }
+ else if (**Index == '\"') /* Direct string ? */
+ {
+ if (!TypeKnown) /* Unknown expression type yet ? */
+ {
+ TypeKnown = TRUE; /* Signal: it is a string */
+ SubType = FALSE;
+ }
+ else if (SubType) /* Type was known to be numeric ? */
+ {
+ fprintf (ErrStream, "ERROR in line %d, statement %d - Type conflict in expression\n", BasicLineNo, StatementNo);
+ return (FALSE);
+ }
+ while (**Index == '\"') /* Concatenated strings are ok, since they allow the use of the " character */
+ {
+ while (*(++ (*Index)) != '\"') /* Find closing quote */
+ ;
+ (*Index) ++; /* Step past it */
+ }
+ if (**Index == '(') /* String is sliced ? */
+ if (!SliceDirectString (BasicLineNo, StatementNo, Keyword, Index))
+ return (FALSE);
+ }
+ else if (ScanVariable (BasicLineNo, StatementNo, Keyword, Index, &SubSubType, &VarNameLen, 1)) /* Is it a variable ? */
+ {
+ if (!TypeKnown) /* Unknown expression type yet ? */
+ {
+ TypeKnown = TRUE; /* Signal: it is string */
+ SubType = SubSubType;
+ }
+ else if (SubType != SubSubType) /* Different type variable ? */
+ {
+ fprintf (ErrStream, "ERROR in line %d, statement %d - Type conflict in expression\n", BasicLineNo, StatementNo);
+ return (FALSE);
+ }
+ }
+ else if (VarNameLen != 0) /* (Not a variable) */
+ return (FALSE); /* (But an error that was already reported) */
+ /* It's none of the above. Go check tokens */
+ else switch (TokenMap[**Index].TokenType)
+ {
+ case 0 : fprintf (ErrStream, "ERROR in line %d, statement %d - Unexpected token \"%s\"\n",
+ BasicLineNo, StatementNo, TokenMap[**Index].Token);
+ return (FALSE);
+ case 1 :
+ case 2 : fprintf (ErrStream, "ERROR in line %d, statement %d - Unexpected keyword \"%s\"\n",
+ BasicLineNo, StatementNo, TokenMap[**Index].Token);
+ return (FALSE);
+ case 3 :
+ case 4 :
+ case 5 : ThisToken = *((*Index) ++);
+ if (TokenMap[ThisToken].TokenType == 5)
+ {
+ if (Keyword != 0xF5 && Keyword != 0xE0) /* Not handling a PRINT or LPRINT ? */
+ {
+ fprintf (ErrStream, "ERROR in line %d, statement %d - Unexpected token \"%s\"\n",
+ BasicLineNo, StatementNo, TokenMap[**Index].Token);
+ return (FALSE);
+ }
+ }
+ else if (ThisToken == 0xC0 && **Index == '\"') /* Special: USR "x" */
+ {
+ (*Index) ++; /* (Step past the opening quote) */
+ if (CheckEnd (BasicLineNo, StatementNo, Index))
+ return (FALSE);
+ if (toupper (**Index) < 'A' || toupper (**Index) > 'U') /* Bad UDG character ? */
+ {
+ fprintf (ErrStream, "ERROR in line %d, statement %d - Bad UDG \"%s\"\n",
+ BasicLineNo, StatementNo, TokenMap[**Index].Token);
+ return (FALSE);
+ }
+ (*Index) ++;
+ if (CheckEnd (BasicLineNo, StatementNo, Index))
+ return (FALSE);
+ if (**Index != '\"') /* More than one letter ? */
+ {
+ fprintf (ErrStream, "ERROR in line %d, statement %d - An UDG name may be only 1 letter\n",
+ BasicLineNo, StatementNo);
+ return (FALSE);
+ }
+ (*Index) --;
+ if (toupper (**Index) == 'T' || toupper (**Index) == 'U') /* One of the UDGs 'T' or 'U' ? */
+ switch (Is48KProgram) /* Then the program must be 48K */
+ {
+ case -1 : Is48KProgram = 1; break; /* Set the flag */
+ case 0 : fprintf (ErrStream, "ERROR - Line %d contains UDGs \'T\' and/or \'U\'\n"
+ "but the program was already marked 128K\n", BasicLineNo);
+ return (FALSE);
+ case 1 : break;
+ }
+ (*Index) += 2; /* Step past the UDG name and closing quote */
+ break; /* Done, step out */
+ }
+ else
+ {
+ if (!TypeKnown) /* Unknown expression type yet ? */
+ {
+ TypeKnown = TRUE; /* Set expected type */
+ SubType = (TokenMap[ThisToken].TokenType == 3);
+ }
+ else if ((SubType && TokenMap[ThisToken].TokenType == 4) ||
+ (!SubType && TokenMap[ThisToken].TokenType == 3))
+ {
+ fprintf (ErrStream, "ERROR in line %d, statement %d - Type conflict in expression\n", BasicLineNo, StatementNo);
+ return (FALSE);
+ }
+ }
+ ClassIndex = -1;
+ while (TokenMap[ThisToken].KeywordClass[++ ClassIndex]) /* Handle all class parameters */
+ {
+ if (CheckEnd (BasicLineNo, StatementNo, Index))
+ return (FALSE);
+ else if (TokenMap[ThisToken].KeywordClass[ClassIndex] >= 32) /* Required token or class ? */
+ {
+ if (**Index != TokenMap[ThisToken].KeywordClass[ClassIndex]) /* (Required token) */
+ { /* (Token not there) */
+ fprintf (ErrStream, "ERROR in line %d, statement %d - Expected \"%c\", but got \"%s\"\n",
+ BasicLineNo, StatementNo, TokenMap[ThisToken].KeywordClass[ClassIndex], TokenMap[**Index].Token);
+ return (FALSE);
+ }
+ else
+ {
+ if (**Index == '(')
+ {
+#ifdef __DEBUG__
+ printf ("DEBUG - %sTurning on token bracket\n", ListSpaces);
+#endif
+ TokenBracket = TRUE;
+ }
+ else if (**Index == ')')
+ {
+#ifdef __DEBUG__
+ printf ("DEBUG - %sTurning off token bracket\n", ListSpaces);
+#endif
+ TokenBracket = FALSE;
+ }
+ (*Index) ++;
+ }
+ }
+ else /* (Command class) */
+ switch (TokenMap[ThisToken].KeywordClass[ClassIndex])
+ {
+ case 1 : if (!HandleClass01 (BasicLineNo, StatementNo, ThisToken, Index, &Dummy)) /* (Special: FN) */
+ return (FALSE);
+ break;
+ case 3 : if (!HandleClass03 (BasicLineNo, StatementNo, ThisToken, Index))
+ return (FALSE);
+ break;
+ case 5 : if (!HandleClass05 (BasicLineNo, StatementNo, ThisToken, Index))
+ return (FALSE);
+ break;
+ case 6 : if (!HandleClass06 (BasicLineNo, StatementNo, ThisToken, Index))
+ return (FALSE);
+ break;
+ case 8 : if (!HandleClass08 (BasicLineNo, StatementNo, ThisToken, Index))
+ return (FALSE);
+ break;
+ case 10 : if (!HandleClass10 (BasicLineNo, StatementNo, ThisToken, Index))
+ return (FALSE);
+ break;
+ case 12 : if (!HandleClass12 (BasicLineNo, StatementNo, ThisToken, Index))
+ return (FALSE);
+ break;
+ case 13 : if (!HandleClass13 (BasicLineNo, StatementNo, ThisToken, Index))
+ return (FALSE);
+ break;
+ case 14 : if (!HandleClass14 (BasicLineNo, StatementNo, ThisToken, Index))
+ return (FALSE);
+ break;
+ }
+ }
+ if (ThisToken == 0xA6) /* INKEY$ ? */
+ if (**Index == '#') /* Type 'INKEY$#<stream>' ? */
+ {
+ (*Index) ++;
+ if (!ScanStream (BasicLineNo, StatementNo, ThisToken, Index))
+ return (FALSE);
+ }
+ break;
+ }
+ /* Piece done, continue */
+ if (More)
+ {
+ if (**Index == 0xC5 || **Index == 0xC6) /* ('OR' and 'AND') */
+ {
+#ifdef __DEBUG__
+ printf ("DEBUG - %sRecurse ScanExpression for \"%s\"\n", ListSpaces, TokenMap[**Index].Token);
+#endif
+ if (!TotalTypeKnown) /* 'Simple' expression before the AND/OR ? */
+ *Type = SubType;
+ if (**Index == 0xC5 && !*Type)
+ {
+ fprintf (ErrStream, "ERROR in line %d, statement %d - \"OR\" requires a numeric left value\n", BasicLineNo, StatementNo);
+ return (FALSE);
+ }
+ ThisToken = *((*Index) ++); /* Step over the operator - but remember it */
+ if (!ScanExpression (BasicLineNo, StatementNo, ThisToken, Index, &SubSubType, 0)) /* Recurse - at level 0! */
+ return (FALSE);
+ if (!SubSubType) /* The expression at the right must be numeric for both AND and OR */
+ {
+ fprintf (ErrStream, "ERROR in line %d, statement %d - \"%s\" requires a numeric right value\n",
+ BasicLineNo, StatementNo, TokenMap[ThisToken].Token);
+ return (FALSE);
+ }
+ if (!TypeKnown) /* We didn't have an expected type yet ? */
+ {
+ TypeKnown = TRUE;
+ TotalTypeKnown = TRUE;
+ SubType = *Type = (bool)(ThisToken == 0xC6 && !*Type ? FALSE : TRUE); /* Signal resulting type */
+ /* x$ AND y -> result is string */
+ /* x AND y -> result is numeric */
+ /* x OR y -> result is numeric */
+ }
+ More = FALSE; /* (Because the recursing causes the expression to be evaluated right to left, we're done now) */
+ }
+ else if ((**Index == '=' || **Index == '<' || **Index == '>' || /* EXCEPTION: equations between brackets (side effects) */
+ **Index == 0xC7 || **Index == 0xC8 || **Index == 0xC9) && /* ("<=", ">=" and "<>") */
+ Level) /* Not on level 0: that is handled below! */
+ { /* Expressions like 'LET A=(INKEY$="A")'; we're now between these brackets */
+ SubType = *Type = TRUE; /* Signal: result is going to be numeric */
+ TotalTypeKnown = TRUE;
+ TypeKnown = FALSE; /* Start with a fresh subexpression type */
+ (*Index) ++;
+ }
+ else if ((TokenMap[Keyword].TokenType != 4 && TokenMap[Keyword].TokenType != 3) || /* Not evaluating an expression token ? */
+ TokenBracket) /* Or evaluating an operand of a token ? */
+ {
+ if (**Index == '+') /* (Can apply to both string and numeric expressions) */
+ (*Index) ++;
+ else if (**Index == '-' || **Index == '*' || **Index == '/' || **Index == '^') /* (Numeric only) */
+ {
+ if (!SubType) /* Type was known to be string ? */
+ {
+ fprintf (ErrStream, "ERROR in line %d, statement %d - Type conflict in expression\n", BasicLineNo, StatementNo);
+ return (FALSE);
+ }
+ (*Index) ++;
+ }
+ /* Equations and logical operators turn the total result numeric, but each subexpression may be of any type */
+ else if ((**Index == '=' || **Index == '<' || **Index == '>' ||
+ **Index == 0xC7 || **Index == 0xC8 || **Index == 0xC9) && /* ("<=", ">=" and "<>") */
+ !Level) /* Only evaluate these on level 0! */
+ {
+ TotalTypeKnown = TRUE;
+ *Type = TRUE; /* Signal: result is going to be numeric */
+ TypeKnown = FALSE; /* Start with a fresh subexpression type */
+ (*Index) ++;
+ }
+ else
+ More = FALSE;
+ }
+ else
+ More = FALSE;
+ }
+ }
+ if (!TotalTypeKnown) /* 'Simple' expression ? */
+ *Type = SubType; /* Set return type */
+#ifdef __DEBUG__
+ printf ("DEBUG - %sLeave ScanExpression, Type is %s, next char is \"%s\"\n",
+ ListSpaces, *Type ? "NUM" : "ALPHA", TokenMap[**Index].Token);
+ if (-- RecurseLevel > 0)
+ memset (ListSpaces, ' ', RecurseLevel * 2);
+ ListSpaces[RecurseLevel * 2] = '\0';
+#endif
+ return (TRUE);
+}
+
+bool HandleClass01 (int BasicLineNo, int StatementNo, int Keyword, byte **Index, bool *Type)
+
+/**********************************************************************************************************************************/
+/* Class 1 = Used in LET. A variable is required. */
+/* `Type' is returned to handle the rest of this special statement (HandleClass02) */
+/* This function is also used to parse the variable name for DIM and FN. */
+/**********************************************************************************************************************************/
+
+{
+ int VarNameLen;
+ int ParseArray;
+
+#ifdef __DEBUG__
+ printf ("DEBUG - %sLine %d, statement %d, Enter Class 1, keyword \"%s\", next is \"%s\"\n",
+ ListSpaces, BasicLineNo, StatementNo, TokenMap[Keyword].Token, TokenMap[**Index].Token);
+#endif
+ ParseArray = ((Keyword == 0xA8 || Keyword == 0xE9) ? -1 : 2); /* Do not parse any bracketing if checking DIM or FN */
+ if (!ScanVariable (BasicLineNo, StatementNo, Keyword, Index, Type, &VarNameLen, ParseArray))
+ {
+ if (VarNameLen == 0)
+ BADTOKEN ("variable", TokenMap[**Index].Token);
+ return (FALSE);
+ }
+ return (TRUE);
+}
+
+bool HandleClass02 (int BasicLineNo, int StatementNo, int Keyword, byte **Index, bool Type)
+
+/**********************************************************************************************************************************/
+/* Class 2 = Used in LET. An expression, numeric or string, must follow. */
+/* `Type' is the type as returned previously by the HandleClass01 call */
+/**********************************************************************************************************************************/
+
+{
+ bool SubType;
+
+#ifdef __DEBUG__
+ printf ("DEBUG - %sLine %d, statement %d, Enter Class 2, keyword \"%s\", next is \"%s\"\n",
+ ListSpaces, BasicLineNo, StatementNo, TokenMap[Keyword].Token, TokenMap[**Index].Token);
+#endif
+ if (!ScanExpression (BasicLineNo, StatementNo, Keyword, Index, &SubType, 0))
+ return (FALSE);
+ if (SubType != Type) /* Must match */
+ {
+ fprintf (ErrStream, "ERROR in line %d, statement %d - Bad assignment expression type\n", BasicLineNo, StatementNo);
+ return (FALSE);
+ }
+ return (TRUE);
+}
+
+bool HandleClass03 (int BasicLineNo, int StatementNo, int Keyword, byte **Index)
+
+/**********************************************************************************************************************************/
+/* Class 3 = A numeric expression may follow. Zero to be used in case of default. */
+/**********************************************************************************************************************************/
+
+{
+#ifdef __DEBUG__
+ printf ("DEBUG - %sLine %d, statement %d, Enter Class 3, keyword \"%s\", next is \"%s\"\n",
+ ListSpaces, BasicLineNo, StatementNo, TokenMap[Keyword].Token, TokenMap[**Index].Token);
+#endif
+ if (**Index == ':' || **Index == 0x0D) /* No expression following ? */
+ return (TRUE); /* Then we're done already */
+ if (Keyword == 0xFD && **Index == '#') /* EXCEPTION: CLEAR may take a stream rather than a numeric expression */
+ {
+ (*Index) ++;
+ if (!SignalInterface1 (BasicLineNo, StatementNo, 0)) /* (Which is Interface1/Opus specific) */
+ return (FALSE);
+ if (**Index == ':' || **Index == 0x0D) /* No expression following ? */
+ return (TRUE); /* (An empty stream is allowed as well - it clears all streams at once) */
+ }
+ return (HandleClass06 (BasicLineNo, StatementNo, Keyword, Index)); /* Find numeric expression */
+}
+
+bool HandleClass04 (int BasicLineNo, int StatementNo, int Keyword, byte **Index)
+
+/**********************************************************************************************************************************/
+/* Class 4 = A single character variable must follow. */
+/**********************************************************************************************************************************/
+
+{
+ bool Type;
+ int VarNameLen;
+
+#ifdef __DEBUG__
+ printf ("DEBUG - %sLine %d, statement %d, Enter Class 4, keyword \"%s\", next is \"%s\"\n",
+ ListSpaces, BasicLineNo, StatementNo, TokenMap[Keyword].Token, TokenMap[**Index].Token);
+#endif
+ if (!ScanVariable (BasicLineNo, StatementNo, Keyword, Index, &Type, &VarNameLen, 0))
+ {
+ if (VarNameLen == 0)
+ BADTOKEN ("variable", TokenMap[**Index].Token);
+ return (FALSE);
+ }
+ if (VarNameLen != 1 || !Type) /* Not single letter or not a numeric variable ? */
+ {
+ fprintf (ErrStream, "ERROR in line %d, statement %d - Wrong variable type\n", BasicLineNo, StatementNo);
+ return (FALSE);
+ }
+ return (TRUE);
+}
+
+bool HandleClass05 (int BasicLineNo, int StatementNo, int Keyword, byte **Index)
+
+/**********************************************************************************************************************************/
+/* Class 5 = A set of items may be given. */
+/**********************************************************************************************************************************/
+
+{
+ bool Type;
+ bool More = TRUE;
+ int VarNameLen;
+
+#ifdef __DEBUG__
+ printf ("DEBUG - %sLine %d, statement %d, Enter Class 5, keyword \"%s\", next is \"%s\"\n",
+ ListSpaces, BasicLineNo, StatementNo, TokenMap[Keyword].Token, TokenMap[**Index].Token);
+#endif
+ while (More)
+ {
+ while (**Index == ';' || **Index == ',' || **Index == '\'') /* One of the separator characters ? */
+ (*Index) ++; /* (More than one may follow) */
+ if (**Index == ':' || **Index == 0x0D) /* End of statement or end of line ? */
+ More = FALSE;
+ else if (**Index == '#') /* A stream ? */
+ {
+ (*Index) ++; /* (Step past the '#' mark) */
+ if (!ScanStream (BasicLineNo, StatementNo, Keyword, Index))
+ return (FALSE);
+ }
+ else if (TokenMap[**Index].TokenType == 2 || /* A colour parameter ? */
+ **Index == 0xAD) /* TAB ? */
+ {
+ (*Index) ++; /* (Skip the token) */
+ if (!HandleClass06 (BasicLineNo, StatementNo, Keyword, Index)) /* Find parameter (numeric expression) */
+ return (FALSE);
+ }
+ else if (**Index == 0xAC) /* AT ? */
+ {
+ (*Index) ++; /* (Skip the token) */
+ if (!HandleClass06 (BasicLineNo, StatementNo, Keyword, Index)) /* Find first parameter (numeric expression) */
+ return (FALSE);
+ if (CheckEnd (BasicLineNo, StatementNo, Index))
+ return (FALSE);
+ if (**Index != ',') /* (Required separator token) */
+ {
+ BADTOKEN ("\",\"", TokenMap[**Index].Token);
+ return (FALSE);
+ }
+ (*Index) ++; /* (Skip the token) */
+ if (!HandleClass06 (BasicLineNo, StatementNo, Keyword, Index)) /* Find second parameter (numeric expression) */
+ return (FALSE);
+ }
+ else if (Keyword == 0xEE && **Index == 0xCA) /* INPUT may use LINE */
+ {
+ (*Index) ++; /* (Skip the token) */
+ if (!ScanVariable (BasicLineNo, StatementNo, Keyword, Index, &Type, &VarNameLen, 0))
+ {
+ if (VarNameLen == 0)
+ BADTOKEN ("variable", TokenMap[**Index].Token);
+ return (FALSE);
+ }
+ if (Type) /* Not a alphanumeric variable ? */
+ {
+ fprintf (ErrStream, "ERROR in line %d, statement %d - INPUT LINE requires an alphanumeric variable\n",
+ BasicLineNo, StatementNo);
+ return (FALSE);
+ }
+ }
+ else if (!ScanExpression (BasicLineNo, StatementNo, Keyword, Index, &Type, 0)) /* Get expression */
+ return (FALSE);
+ }
+ return (TRUE);
+}
+
+bool HandleClass06 (int BasicLineNo, int StatementNo, int Keyword, byte **Index)
+
+/**********************************************************************************************************************************/
+/* Class 6 = A numeric expression must follow. */
+/**********************************************************************************************************************************/
+
+{
+ bool Type = FALSE;
+
+#ifdef __DEBUG__
+ printf ("DEBUG - %sLine %d, statement %d, Enter Class 6, keyword \"%s\", next is \"%s\"\n",
+ ListSpaces, BasicLineNo, StatementNo, TokenMap[Keyword].Token, TokenMap[**Index].Token);
+#endif
+ if (!ScanExpression (BasicLineNo, StatementNo, Keyword, Index, &Type, 0)) /* Get expression */
+ return (FALSE);
+ if (!Type && Keyword != 0xC0) /* Must be numeric */
+ {
+ fprintf (ErrStream, "ERROR in line %d, statement %d - Expected numeric expression\n", BasicLineNo, StatementNo);
+ return (FALSE);
+ }
+ return (TRUE);
+}
+
+bool HandleClass07 (int BasicLineNo, int StatementNo, int Keyword, byte **Index)
+
+/**********************************************************************************************************************************/
+/* Class 7 = Handles colour items. */
+/* Effectively the same as Class 6 */
+/**********************************************************************************************************************************/
+
+{
+#ifdef __DEBUG__
+ printf ("DEBUG - %sLine %d, statement %d, Enter Class 7, keyword \"%s\", next is \"%s\"\n",
+ ListSpaces, BasicLineNo, StatementNo, TokenMap[Keyword].Token, TokenMap[**Index].Token);
+#endif
+ return (HandleClass06 (BasicLineNo, StatementNo, Keyword, Index)); /* Find numeric expression */
+}
+
+bool HandleClass08 (int BasicLineNo, int StatementNo, int Keyword, byte **Index)
+
+/**********************************************************************************************************************************/
+/* Class 8 = Two numeric expressions, separated by a comma, must follow. */
+/**********************************************************************************************************************************/
+
+{
+#ifdef __DEBUG__
+ printf ("DEBUG - %sLine %d, statement %d, Enter Class 8, keyword \"%s\", next is \"%s\"\n",
+ ListSpaces, BasicLineNo, StatementNo, TokenMap[Keyword].Token, TokenMap[**Index].Token);
+#endif
+ if (!HandleClass06 (BasicLineNo, StatementNo, Keyword, Index)) /* Find first numeric expression */
+ return (FALSE);
+ if (**Index != ',')
+ {
+ BADTOKEN ("\",\"", TokenMap[**Index].Token);
+ return (FALSE);
+ }
+ (*Index) ++;
+ return (HandleClass06 (BasicLineNo, StatementNo, Keyword, Index)); /* Find second numeric expression */
+}
+
+bool HandleClass09 (int BasicLineNo, int StatementNo, int Keyword, byte **Index)
+
+/**********************************************************************************************************************************/
+/* Class 9 = As for class 8 but colour items may precede the expression. */
+/* Used only by PLOT and DRAW. Colour items are TokenType 2 */
+/**********************************************************************************************************************************/
+
+{
+ bool CheckColour = TRUE;
+
+#ifdef __DEBUG__
+ printf ("DEBUG - %sLine %d, statement %d, Enter Class 9, keyword \"%s\", next is \"%s\"\n",
+ ListSpaces, BasicLineNo, StatementNo, TokenMap[Keyword].Token, TokenMap[**Index].Token);
+#endif
+ while (CheckColour)
+ {
+ if (CheckEnd (BasicLineNo, StatementNo, Index))
+ return (FALSE);
+ if (TokenMap[**Index].TokenType == 2) /* A colour parameter ? */
+ {
+ (*Index) ++; /* Skip the token */
+ if (!HandleClass06 (BasicLineNo, StatementNo, Keyword, Index)) /* Find parameter (numeric expression) */
+ return (FALSE);
+ if (CheckEnd (BasicLineNo, StatementNo, Index))
+ return (FALSE);
+ if (**Index != ';') /* All colour parameters must be separated with semicolons */
+ {
+ BADTOKEN ("\";\"", TokenMap[**Index].Token);
+ return (FALSE);
+ }
+ (*Index) ++; /* Skip the ";' */
+ }
+ else
+ CheckColour = FALSE;
+ }
+ if (CheckEnd (BasicLineNo, StatementNo, Index))
+ return (FALSE);
+ return (HandleClass08 (BasicLineNo, StatementNo, Keyword, Index));
+}
+
+bool HandleClass10 (int BasicLineNo, int StatementNo, int Keyword, byte **Index)
+
+/**********************************************************************************************************************************/
+/* Class 10 = A string expression must follow. */
+/**********************************************************************************************************************************/
+
+{
+ bool Type;
+
+#ifdef __DEBUG__
+ printf ("DEBUG - %sLine %d, statement %d, Enter Class 10, keyword \"%s\", next is \"%s\"\n",
+ ListSpaces, BasicLineNo, StatementNo, TokenMap[Keyword].Token, TokenMap[**Index].Token);
+#endif
+ if (!ScanExpression (BasicLineNo, StatementNo, Keyword, Index, &Type, 0)) /* Get expression */
+ return (FALSE);
+ if (Type) /* Must be string */
+ {
+ fprintf (ErrStream, "ERROR in line %d, statement %d - Expected string expression\n", BasicLineNo, StatementNo);
+ return (FALSE);
+ }
+ return (TRUE);
+}
+
+bool HandleClass11 (int BasicLineNo, int StatementNo, int Keyword, byte **Index)
+
+/**********************************************************************************************************************************/
+/* Class 11 = Handles cassette routines. */
+/**********************************************************************************************************************************/
+
+{
+ bool Type;
+ int VarNameLen;
+ int MoveLoop;
+ byte WhichChannel = '\0'; /* (Default is no channel; for tape) */
+
+#ifdef __DEBUG__
+ printf ("DEBUG - %sLine %d, statement %d, Enter Class 11, keyword \"%s\", next is \"%s\"\n",
+ ListSpaces, BasicLineNo, StatementNo, TokenMap[Keyword].Token, TokenMap[**Index].Token);
+#endif
+ switch (Keyword)
+ {
+ case 0xEF: /* (LOAD) */
+ case 0xD6: /* (VERIFY) */
+ case 0xD5: if (**Index == '*') /* (MERGE) */
+ {
+ (*Index) ++;
+ if (!ScanChannel (BasicLineNo, StatementNo, Keyword, Index, &WhichChannel))
+ return (FALSE);
+ if (WhichChannel != 'm' && WhichChannel != 'b' && WhichChannel != 'n')
+ {
+ fprintf (ErrStream, "ERROR in line %d, statement %d - You cannot LOAD/VERIFY/MERGE from the \"%s\" channel\n",
+ BasicLineNo, StatementNo, TokenMap[WhichChannel].Token);
+ return (FALSE);
+ }
+ }
+ else if (**Index == '!') /* 128K RAM-bank ? */
+ {
+ (*Index) ++;
+ switch (Is48KProgram) /* Then the program must be 128K */
+ {
+ case -1 : Is48KProgram = 0; break; /* Set the flag */
+ case 1 : fprintf (ErrStream, "ERROR - Line %d contains 128K file I/O, but the program\n"
+ "also uses UDGs \'T\' and/or \'U\'\n", BasicLineNo);
+ return (FALSE);
+ case 0 : break;
+ }
+ }
+ if (WhichChannel != '\0' && WhichChannel != 'm') /* Not tape nor microdrive/disk channel ? */
+ {
+ if (**Index != ':' && **Index != 0x0D && /* (End of statement) */
+ **Index != 0xAF && /* (CODE) */
+ **Index != 0xE4 && /* (DATA) */
+ **Index != 0xCA && /* (LINE) */
+ **Index != 0xAA) /* (SCREEN$) */
+ {
+ fprintf (ErrStream, "ERROR in line %d, statement %d - The \"%s\" channel does not use filenames\n",
+ BasicLineNo, StatementNo, TokenMap[WhichChannel].Token);
+ return (FALSE);
+ }
+ }
+ else
+ {
+ if (**Index == '\"') /* Look for a filename */
+ {
+ while (**Index == '\"') /* Concatenated strings are ok, since they allow the use of the " character */
+ { /* (And an empty string is allowed here as well) */
+ while (*(++ (*Index)) != '\"') /* Find closing quote */
+ if (**Index == 0x0D) /* End of line ? */
+ {
+ fprintf (ErrStream, "ERROR in line %d, statement %d - Unexpected end of line\n", BasicLineNo, StatementNo);
+ return (FALSE);
+ }
+ (*Index) ++; /* Step past it */
+ }
+ }
+ else if (**Index == ':' || **Index == 0x0D || /* (End of statement) */
+ **Index == 0xAF || /* (CODE) */
+ **Index == 0xE4 || /* (DATA) */
+ **Index == 0xCA || /* (LINE) */
+ **Index == 0xAA) /* (SCREEN$) */
+ {
+ BADTOKEN ("filename", TokenMap[**Index].Token);
+ return (FALSE);
+ }
+ else if (!HandleClass10 (BasicLineNo, StatementNo, Keyword, Index)) /* Look for a string expression */
+ return (FALSE);
+ }
+ if (**Index != ':' && **Index != 0x0D) /* (Continue unless end of statement) */
+ {
+ if (**Index == 0xAF) /* CODE */
+ {
+ if (Keyword == 0xD5) /* (We were doing MERGE ?) */
+ {
+ fprintf (ErrStream, "ERROR in line %d, statement %d - Cannot MERGE CODE\n", BasicLineNo, StatementNo);
+ return (FALSE);
+ }
+ (*Index) ++;
+ if (**Index != ':' && **Index != 0x0D) /* Optional address ? */
+ {
+ if (!HandleClass06 (BasicLineNo, StatementNo, Keyword, Index)) /* Find address (numeric expression) */
+ return (FALSE);
+ if (**Index == ',') /* Also optional length ? */
+ {
+ (*Index) ++;
+ if (!HandleClass06 (BasicLineNo, StatementNo, Keyword, Index)) /* Find length (numeric expression) */
+ return (FALSE);
+ }
+ else if (**Index != ':' && **Index != 0x0D)
+ {
+ BADTOKEN ("\",\"", TokenMap[**Index].Token);
+ return (FALSE);
+ }
+ }
+ }
+ else if (**Index == 0xAA) /* SCREEN$ */
+ (*Index) ++;
+ else if (**Index == 0xE4) /* DATA */
+ {
+ (*Index) ++;
+ if (CheckEnd (BasicLineNo, StatementNo, Index))
+ return (FALSE);
+ if (!ScanVariable (BasicLineNo, StatementNo, Keyword, Index, &Type, &VarNameLen, -1))
+ {
+ if (VarNameLen == 0)
+ BADTOKEN ("variable", TokenMap[**Index].Token);
+ return (FALSE);
+ }
+ if (VarNameLen != 1) /* Not single letter ? */
+ {
+ fprintf (ErrStream, "ERROR in line %d, statement %d - Wrong variable type; must be single character\n",
+ BasicLineNo, StatementNo);
+ return (FALSE);
+ }
+ if (**Index != '(') /* The variable must be followed by an empty index */
+ {
+ fprintf (ErrStream, "ERROR in line %d, statement %d - DATA requires an array\n",
+ BasicLineNo, StatementNo);
+ return (FALSE);
+ }
+ (*Index) ++;
+ if (**Index != ')')
+ {
+ fprintf (ErrStream, "ERROR in line %d, statement %d - DATA requires an empty array index\n",
+ BasicLineNo, StatementNo);
+ return (FALSE);
+ }
+ (*Index) ++;
+ }
+ else
+ {
+ fprintf (ErrStream, "ERROR in line %d, statement %d - Unknown file-type \"%s\"\n",
+ BasicLineNo, StatementNo, TokenMap[**Index].Token);
+ return (FALSE);
+ }
+ }
+ break;
+ case 0xF8: if (**Index == '*') /* (SAVE) */
+ {
+ (*Index) ++;
+ if (!ScanChannel (BasicLineNo, StatementNo, Keyword, Index, &WhichChannel))
+ return (FALSE);
+ if (WhichChannel != 'm' && WhichChannel != 'b' && WhichChannel != 'n')
+ {
+ fprintf (ErrStream, "ERROR in line %d, statement %d - You cannot SAVE to the \"%s\" channel\n",
+ BasicLineNo, StatementNo, TokenMap[WhichChannel].Token);
+ return (FALSE);
+ }
+ }
+ else if (**Index == '!') /* 128K RAM-bank ? */
+ {
+ (*Index) ++;
+ switch (Is48KProgram) /* Then the program must be 128K */
+ {
+ case -1 : Is48KProgram = 0; break; /* Set the flag */
+ case 1 : fprintf (ErrStream, "ERROR - Line %d contains 128K file I/O, but the program\n"
+ "also uses UDGs \'T\' and/or \'U\'\n", BasicLineNo);
+ return (FALSE);
+ case 0 : break;
+ }
+ }
+ if (WhichChannel != '\0' && WhichChannel != 'm') /* Not tape nor microdrive/disk channel ? */
+ {
+ if (**Index != ':' && **Index != 0x0D && /* (End of statement) */
+ **Index != 0xAF && /* (CODE) */
+ **Index != 0xE4 && /* (DATA) */
+ **Index != 0xCA && /* (LINE) */
+ **Index != 0xAA) /* (SCREEN$) */
+ {
+ fprintf (ErrStream, "ERROR in line %d, statement %d - The \"%s\" channel does not use filenames\n",
+ BasicLineNo, StatementNo, TokenMap[WhichChannel].Token);
+ return (FALSE);
+ }
+ }
+ else
+ {
+ if (**Index == '\"') /* Look for a filename */
+ {
+ if (*(*Index + 1) == '\"' && /* Empty string (not allowed) ? */
+ *(*Index + 2) != '\"') /* Concatenation - first char is a " (allowed) ? */
+ {
+ fprintf (ErrStream, "ERROR in line %d, statement %d - Empty filename not allowed\n", BasicLineNo, StatementNo);
+ return (FALSE);
+ }
+ while (**Index == '\"') /* Concatenated strings are ok, since they allow the use of the " character */
+ {
+ while (*(++ (*Index)) != '\"') /* Find closing quote */
+ if (**Index == 0x0D) /* End of line ? */
+ {
+ fprintf (ErrStream, "ERROR in line %d, statement %d - Unexpected end of line\n", BasicLineNo, StatementNo);
+ return (FALSE);
+ }
+ (*Index) ++; /* Step past it */
+ }
+ }
+ else if (**Index == ':' || **Index == 0x0D || /* (End of statement) */
+ **Index == 0xAF || /* (CODE) */
+ **Index == 0xE4 || /* (DATA) */
+ **Index == 0xCA || /* (LINE) */
+ **Index == 0xAA) /* (SCREEN$) */
+ {
+ BADTOKEN ("filename", TokenMap[**Index].Token);
+ return (FALSE);
+ }
+ else if (!HandleClass10 (BasicLineNo, StatementNo, Keyword, Index)) /* Look for a string expression */
+ return (FALSE);
+ }
+ if (**Index != ':' && **Index != 0x0D) /* (Continue unless end of statement) */
+ {
+ if (**Index == 0xAF) /* CODE */
+ {
+ (*Index) ++;
+ if (!HandleClass06 (BasicLineNo, StatementNo, Keyword, Index)) /* Find address (numeric expression) */
+ return (FALSE);
+ if (**Index != ',')
+ {
+ fprintf (ErrStream, "ERROR in line %d, statement %d - %s CODE requires both address and length\n",
+ BasicLineNo, StatementNo, TokenMap[Keyword].Token);
+ return (FALSE);
+ }
+ (*Index) ++;
+ if (!HandleClass06 (BasicLineNo, StatementNo, Keyword, Index)) /* Find length (numeric expression) */
+ return (FALSE);
+ }
+ else if (**Index == 0xE4) /* DATA */
+ {
+ (*Index) ++;
+ if (CheckEnd (BasicLineNo, StatementNo, Index))
+ return (FALSE);
+ if (!ScanVariable (BasicLineNo, StatementNo, Keyword, Index, &Type, &VarNameLen, -1))
+ {
+ if (VarNameLen == 0)
+ BADTOKEN ("variable", TokenMap[**Index].Token);
+ return (FALSE);
+ }
+ if (VarNameLen != 1) /* Not single letter ? */
+ {
+ fprintf (ErrStream, "ERROR in line %d, statement %d - Wrong variable type; must be single character\n",
+ BasicLineNo, StatementNo);
+ return (FALSE);
+ }
+ if (**Index != '(') /* The variable must be followed by an empty index */
+ {
+ fprintf (ErrStream, "ERROR in line %d, statement %d - DATA requires an array\n",
+ BasicLineNo, StatementNo);
+ return (FALSE);
+ }
+ (*Index) ++;
+ if (**Index != ')')
+ {
+ fprintf (ErrStream, "ERROR in line %d, statement %d - DATA requires an empty array index\n",
+ BasicLineNo, StatementNo);
+ return (FALSE);
+ }
+ (*Index) ++;
+ }
+ else if (**Index == 0xAA) /* SCREEN$ */
+ (*Index) ++;
+ else if (**Index == 0xCA) /* LINE */
+ {
+ (*Index) ++;
+ if (!HandleClass06 (BasicLineNo, StatementNo, Keyword, Index)) /* Find starting line (numeric expression) */
+ return (FALSE);
+ }
+ else
+ {
+ fprintf (ErrStream, "ERROR in line %d, statement %d - Unknown file-type \"%s\"\n",
+ BasicLineNo, StatementNo, TokenMap[**Index].Token);
+ return (FALSE);
+ }
+ }
+ break;
+ case 0xCF: if (!SignalInterface1 (BasicLineNo, StatementNo, 0)) /* (CAT) */
+ return (FALSE);
+ if (**Index == '#') /* A stream may precede the drive number */
+ {
+ (*Index) ++;
+ if (!ScanStream (BasicLineNo, StatementNo, Keyword, Index))
+ return (FALSE);
+ if (**Index != ',') /* (Required separator token) */
+ {
+ BADTOKEN ("\",\"", TokenMap[**Index].Token);
+ return (FALSE);
+ }
+ }
+ if (!HandleClass06 (BasicLineNo, StatementNo, Keyword, Index)) /* Find drive number (numeric expression) */
+ return (FALSE);
+ break;
+ case 0xD0: if (!ScanChannel (BasicLineNo, StatementNo, Keyword, Index, &WhichChannel)) /* (FORMAT) */
+ return (FALSE);
+ switch (WhichChannel)
+ {
+ case 'm' : if (CheckEnd (BasicLineNo, StatementNo, Index)) /* "m" requires an additional new volume name */
+ return (FALSE);
+ if (**Index == '\"') /* Look for a volume name */
+ {
+ if (*(*Index + 1) == '\"' && /* Empty string (not allowed) ? */
+ *(*Index + 2) != '\"') /* Concatenation - first char is a " (allowed) ? */
+ {
+ fprintf (ErrStream, "ERROR in line %d, statement %d - Empty volume name not allowed\n",
+ BasicLineNo, StatementNo);
+ return (FALSE);
+ }
+ while (**Index == '\"') /* Concatenated strings are ok, since they allow the use of the " character */
+ {
+ while (*(++ (*Index)) != '\"') /* Find closing quote */
+ if (**Index == 0x0D) /* End of line ? */
+ {
+ fprintf (ErrStream, "ERROR in line %d, statement %d - Unexpected end of line\n",
+ BasicLineNo, StatementNo);
+ return (FALSE);
+ }
+ (*Index) ++; /* Step past it */
+ }
+ }
+ else if (!HandleClass10 (BasicLineNo, StatementNo, Keyword, Index)) /* Look for a string expression */
+ return (FALSE);
+ break;
+ case 't' : /* The port channels requires an additional baud rate */
+ case 'b' :
+ case 'j' : if (**Index != ';') /* The joystick channel requires a operand to turn it on or off */
+ {
+ BADTOKEN ("\";\"", TokenMap[**Index].Token);
+ return (FALSE);
+ }
+ (*Index) ++;
+ if (CheckEnd (BasicLineNo, StatementNo, Index))
+ return (FALSE);
+ if (!HandleClass06 (BasicLineNo, StatementNo, Keyword, Index)) /* Look for a numeric expression */
+ return (FALSE);
+ break;
+ default : fprintf (ErrStream, "ERROR in line %d, statement %d - You cannot FORMAT from the \"%s\" channel\n",
+ BasicLineNo, StatementNo, TokenMap[WhichChannel].Token);
+ return (FALSE);
+ }
+ break;
+ case 0xD1: for (MoveLoop = 0 ; MoveLoop < 2 ; MoveLoop ++) /* (MOVE) */
+ {
+ if (**Index == '#')
+ {
+ (*Index) ++; /* (Step past the '#' mark) */
+ if (!ScanStream (BasicLineNo, StatementNo, Keyword, Index))
+ return (FALSE);
+ }
+ else
+ {
+ if (!ScanChannel (BasicLineNo, StatementNo, Keyword, Index, &WhichChannel))
+ return (FALSE);
+ switch (WhichChannel)
+ {
+ case 'm' : if (CheckEnd (BasicLineNo, StatementNo, Index)) /* "m" requires an additional filename */
+ return (FALSE);
+ if (**Index == '\"') /* Look for a filename */
+ {
+ if (*(*Index + 1) == '\"' && /* Empty string (not allowed) ? */
+ *(*Index + 2) != '\"') /* Concatenation - first char is a " (allowed) ? */
+ {
+ fprintf (ErrStream, "ERROR in line %d, statement %d - Empty filename not allowed\n",
+ BasicLineNo, StatementNo);
+ return (FALSE);
+ }
+ while (**Index == '\"')
+ {
+ while (*(++ (*Index)) != '\"')
+ if (**Index == 0x0D)
+ {
+ fprintf (ErrStream, "ERROR in line %d, statement %d - Unexpected end of line\n",
+ BasicLineNo, StatementNo);
+ return (FALSE);
+ }
+ (*Index) ++;
+ }
+ }
+ else if (!HandleClass10 (BasicLineNo, StatementNo, Keyword, Index))
+ return (FALSE);
+ break;
+ case 't' :
+ case 'b' :
+ case 'n' :
+ case 'd' : break; /* All these are okay and don't use extra parameters */
+ case 's' : if (MoveLoop == 0) /* The "s" channel is write-only */
+ {
+ fprintf (ErrStream, "ERROR in line %d, statement %d - You cannot MOVE from the \"s\" channel\n",
+ BasicLineNo, StatementNo);
+ return (FALSE);
+ }
+ break;
+ case 'k' : if (MoveLoop == 1) /* The "k" channel is read-only */
+ {
+ fprintf (ErrStream, "ERROR in line %d, statement %d - You cannot MOVE to the \"k\" channel\n",
+ BasicLineNo, StatementNo);
+ return (FALSE);
+ }
+ break;
+ default : fprintf (ErrStream, "ERROR in line %d, statement %d - You cannot MOVE from/to the \"%s\" channel\n",
+ BasicLineNo, StatementNo, TokenMap[WhichChannel].Token);
+ return (FALSE);
+ }
+ }
+ if (MoveLoop == 0)
+ {
+ if (**Index != 0xCC) /* Required token 'TO' */
+ {
+ BADTOKEN ("\"TO\"", TokenMap[**Index].Token);
+ return (FALSE);
+ }
+ (*Index) ++;
+ }
+ }
+ break;
+ case 0xD2: if (**Index == '!') /* (ERASE) */
+ { /* 128K RAM-bank ? */
+ (*Index) ++;
+ switch (Is48KProgram) /* Then the program must be 128K */
+ {
+ case -1 : Is48KProgram = 0; break; /* Set the flag */
+ case 1 : fprintf (ErrStream, "ERROR - Line %d contains 128K file I/O, but the program\n"
+ "also uses UDGs \'T\' and/or \'U\'\n", BasicLineNo);
+ return (FALSE);
+ case 0 : break;
+ }
+ }
+ else
+ {
+ if (!ScanChannel (BasicLineNo, StatementNo, Keyword, Index, &WhichChannel))
+ return (FALSE);
+ if (WhichChannel != 'm')
+ {
+ fprintf (ErrStream, "ERROR in line %d, statement %d - You can only ERASE from the ! or \"m\" channel\n",
+ BasicLineNo, StatementNo);
+ return (FALSE);
+ }
+ }
+ if (CheckEnd (BasicLineNo, StatementNo, Index)) /* Additional filename required */
+ return (FALSE);
+ if (**Index == '\"') /* Look for a filename */
+ {
+ if (*(*Index + 1) == '\"' && /* Empty string (not allowed) ? */
+ *(*Index + 2) != '\"') /* Concatenation - first char is a " (allowed) ? */
+ {
+ fprintf (ErrStream, "ERROR in line %d, statement %d - Empty filename not allowed\n",
+ BasicLineNo, StatementNo);
+ return (FALSE);
+ }
+ while (**Index == '\"')
+ {
+ while (*(++ (*Index)) != '\"')
+ if (**Index == 0x0D)
+ {
+ fprintf (ErrStream, "ERROR in line %d, statement %d - Unexpected end of line\n",
+ BasicLineNo, StatementNo);
+ return (FALSE);
+ }
+ (*Index) ++;
+ }
+ }
+ else if (!HandleClass10 (BasicLineNo, StatementNo, Keyword, Index))
+ return (FALSE);
+ break;
+ case 0xD3: if (!ScanStream (BasicLineNo, StatementNo, Keyword, Index)) /* (OPEN #) */
+ return (FALSE);
+ if (**Index != ';' && **Index != ',') /* (Required token) */
+ {
+ BADTOKEN ("\";\"", TokenMap[**Index].Token);
+ return (FALSE);
+ }
+ (*Index) ++;
+ if (!ScanChannel (BasicLineNo, StatementNo, Keyword, Index, &WhichChannel))
+ return (FALSE);
+ switch (WhichChannel)
+ {
+ case 'm' : if (CheckEnd (BasicLineNo, StatementNo, Index)) /* "m" requires an additional filename */
+ return (FALSE);
+ if (**Index == '\"') /* Look for a filename */
+ {
+ if (*(*Index + 1) == '\"' && /* Empty string (not allowed) ? */
+ *(*Index + 2) != '\"') /* Concatenation - first char is a " (allowed) ? */
+ {
+ fprintf (ErrStream, "ERROR in line %d, statement %d - Empty filename not allowed\n",
+ BasicLineNo, StatementNo);
+ return (FALSE);
+ }
+ while (**Index == '\"')
+ {
+ while (*(++ (*Index)) != '\"')
+ if (**Index == 0x0D)
+ {
+ fprintf (ErrStream, "ERROR in line %d, statement %d - Unexpected end of line\n",
+ BasicLineNo, StatementNo);
+ return (FALSE);
+ }
+ (*Index) ++;
+ }
+ }
+ else if (!HandleClass10 (BasicLineNo, StatementNo, Keyword, Index))
+ return (FALSE);
+ break;
+ case 's' :
+ case 'k' :
+ case 'p' :
+ case 't' :
+ case 'b' :
+ case 'n' :
+ case 0xAF:
+ case 0xCF:
+ case '#' : break; /* All these are okay and don't use extra parameters */
+ default : fprintf (ErrStream, "ERROR in line %d, statement %d - You cannot attach a stream to the \"%s\" "
+ "channel\n", BasicLineNo, StatementNo, TokenMap[WhichChannel].Token);
+ return (FALSE);
+ }
+ if (**Index != ':' && **Index != 0x0D) /* (Continue unless end of statement) */
+ {
+ if (**Index == 0xBF) /* IN */
+ {
+ (*Index) ++;
+ if (!SignalInterface1 (BasicLineNo, StatementNo, 2)) /* This is Opus specific */
+ return (FALSE);
+ }
+ else if (**Index == 0xDF || /* OUT */
+ **Index == 0xB9) /* EXP */
+ {
+ (*Index) ++;
+ if (!SignalInterface1 (BasicLineNo, StatementNo, 2)) /* This is Opus specific */
+ return (FALSE);
+ if (!HandleClass06 (BasicLineNo, StatementNo, Keyword, Index)) /* Find numeric expression */
+ return (FALSE);
+ }
+ else if (**Index == 0xA5) /* RND */
+ {
+ (*Index) ++;
+ if (!SignalInterface1 (BasicLineNo, StatementNo, 2)) /* This is Opus specific */
+ return (FALSE);
+ if (!HandleClass06 (BasicLineNo, StatementNo, Keyword, Index)) /* Find numeric expression */
+ return (FALSE);
+ if (**Index == ',') /* RND may take a second parameter */
+ {
+ (*Index) ++;
+ if (!HandleClass06 (BasicLineNo, StatementNo, Keyword, Index))
+ return (FALSE);
+ }
+ }
+ }
+ break;
+ case 0xD4: if (!ScanStream (BasicLineNo, StatementNo, Keyword, Index)) /* (CLOSE #) */
+ return (FALSE);
+ break;
+ }
+ return (TRUE);
+}
+
+bool HandleClass12 (int BasicLineNo, int StatementNo, int Keyword, byte **Index)
+
+/**********************************************************************************************************************************/
+/* Class 12 = One or more string expressions, separated by commas, must follow. */
+/**********************************************************************************************************************************/
+
+{
+ bool Type;
+ bool More = TRUE;
+
+#ifdef __DEBUG__
+ printf ("DEBUG - %sLine %d, statement %d, Enter Class 12, keyword \"%s\", next is \"%s\"\n",
+ ListSpaces, BasicLineNo, StatementNo, TokenMap[Keyword].Token, TokenMap[**Index].Token);
+#endif
+ while (More)
+ {
+ if (!ScanExpression (BasicLineNo, StatementNo, Keyword, Index, &Type, 0)) /* Find an expression */
+ return (FALSE);
+ if (Type) /* Must be string */
+ {
+ fprintf (ErrStream, "ERROR in line %d, statement %d - \"%s\" requires string parameters\n",
+ BasicLineNo, StatementNo, TokenMap[Keyword].Token);
+ return (FALSE);
+ }
+ if (**Index == ':' || **Index == 0x0D) /* End of statement or end of line ? */
+ More = FALSE;
+ else if (**Index == ',') /* Separator ? */
+ (*Index) ++;
+ else if (**Index != ')')
+ {
+ BADTOKEN ("\",\"", TokenMap[**Index].Token);
+ return (FALSE);
+ }
+ }
+ return (TRUE);
+}
+
+bool HandleClass13 (int BasicLineNo, int StatementNo, int Keyword, byte **Index)
+
+/**********************************************************************************************************************************/
+/* Class 13 = One or more expressions, separated by commas, must follow (DATA, DIM, FN) */
+/**********************************************************************************************************************************/
+
+{
+ bool Type;
+ bool More = TRUE;
+
+#ifdef __DEBUG__
+ printf ("DEBUG - %sLine %d, statement %d, Enter Class 13, keyword \"%s\", next is \"%s\"\n",
+ ListSpaces, BasicLineNo, StatementNo, TokenMap[Keyword].Token, TokenMap[**Index].Token);
+#endif
+ if (**Index == ')' && Keyword == 0xA8) /* FN requires zero or more expressions */
+ return (TRUE); /* (The closing bracket is a required character and stepped over in CheckSyntax) */
+ while (More)
+ {
+ if (!ScanExpression (BasicLineNo, StatementNo, Keyword, Index, &Type, 0)) /* Find an expression */
+ return (FALSE); /* (Don't care about the type) */
+ if (Keyword == 0xE9 && !Type) /* DIM requires numeric dimensions */
+ {
+ fprintf (ErrStream, "ERROR in line %d, statement %d - \"DIM\" requires numeric dimensions\n", BasicLineNo, StatementNo);
+ return (FALSE);
+ }
+ if (Keyword == 0xE9 || Keyword == 0xA8) /* FN and DIM end with a closing bracket */
+ {
+ if (CheckEnd (BasicLineNo, StatementNo, Index))
+ return (FALSE);
+ if (**Index == ')')
+ More = FALSE;
+ }
+ if (**Index == ':' || **Index == 0x0D) /* End of statement or end of line ? */
+ More = FALSE;
+ else if (**Index == ',') /* Separator ? */
+ (*Index) ++;
+ else if (**Index != ')')
+ {
+ BADTOKEN ("\",\"", TokenMap[**Index].Token);
+ return (FALSE);
+ }
+ }
+ return (TRUE);
+}
+
+bool HandleClass14 (int BasicLineNo, int StatementNo, int Keyword, byte **Index)
+
+/**********************************************************************************************************************************/
+/* Class 14 = One or more variables, separated by commas, must follow (READ) */
+/**********************************************************************************************************************************/
+
+{
+ bool Type;
+ bool More = TRUE;
+ int VarNameLen;
+
+#ifdef __DEBUG__
+ printf ("DEBUG - %sLine %d, statement %d, Enter Class 14, keyword \"%s\", next is \"%s\"\n",
+ ListSpaces, BasicLineNo, StatementNo, TokenMap[Keyword].Token, TokenMap[**Index].Token);
+#endif
+ while (More)
+ {
+ if (!ScanVariable (BasicLineNo, StatementNo, Keyword, Index, &Type, &VarNameLen, 2)) /* We need a variable */
+ {
+ if (VarNameLen == 0) /* (Not a variable) */
+ BADTOKEN ("variable", TokenMap[**Index].Token);
+ return (FALSE);
+ }
+ if (**Index == ':' || **Index == 0x0D) /* End of statement or end of line ? */
+ More = FALSE;
+ else if (**Index == ',') /* Separator ? */
+ (*Index) ++;
+ else
+ {
+ BADTOKEN ("\",\"", TokenMap[**Index].Token);
+ return (FALSE);
+ }
+ }
+ return (TRUE);
+}
+
+bool HandleClass15 (int BasicLineNo, int StatementNo, int Keyword, byte **Index)
+
+/**********************************************************************************************************************************/
+/* Class 15 = DEF FN */
+/**********************************************************************************************************************************/
+
+{
+ bool Type;
+ int VarNameLen;
+
+#ifdef __DEBUG__
+ printf ("DEBUG - %sLine %d, statement %d, Enter Class 15, keyword \"%s\", next is \"%s\"\n",
+ ListSpaces, BasicLineNo, StatementNo, TokenMap[Keyword].Token, TokenMap[**Index].Token);
+#endif
+ if (!ScanVariable (BasicLineNo, StatementNo, Keyword, Index, &Type, &VarNameLen, -1))
+ {
+ if (VarNameLen == 0)
+ BADTOKEN ("variable", TokenMap[**Index].Token);
+ return (FALSE);
+ }
+ if (VarNameLen != 1) /* Not single letter ? */
+ {
+ fprintf (ErrStream, "ERROR in line %d, statement %d - Wrong variable type; must be single character\n",
+ BasicLineNo, StatementNo);
+ return (FALSE);
+ }
+ if (**Index == '(') /* Arguments to be passed to the expression while running ? */
+ {
+ (*Index) ++;
+ if (CheckEnd (BasicLineNo, StatementNo, Index))
+ return (FALSE);
+ if (**Index == ')')
+ {
+ fprintf (ErrStream, "ERROR in line %d, statement %d - Empty parameter array not allowed\n", BasicLineNo, StatementNo);
+ return (FALSE);
+ }
+ while (**Index != ')')
+ {
+ if (!ScanVariable (BasicLineNo, StatementNo, Keyword, Index, &Type, &VarNameLen, -1))
+ {
+ if (VarNameLen == 0)
+ BADTOKEN ("variable", TokenMap[**Index].Token);
+ return (FALSE);
+ }
+ if (VarNameLen != 1) /* Not single letter ? */
+ {
+ fprintf (ErrStream, "ERROR in line %d, statement %d - Wrong variable type; must be single character\n",
+ BasicLineNo, StatementNo);
+ return (FALSE);
+ }
+ if (**Index != 0x0E) /* A number (marker) must follow each parameter */
+ {
+ BADTOKEN ("number marker", TokenMap[**Index].Token);
+ return (FALSE);
+ }
+ (*Index) ++; /* (Step past it) */
+ if (CheckEnd (BasicLineNo, StatementNo, Index))
+ return (FALSE);
+ if (**Index != ')')
+ {
+ if (**Index == ',')
+ (*Index) ++;
+ else
+ {
+ BADTOKEN ("\",\"", TokenMap[**Index].Token);
+ return (FALSE);
+ }
+ }
+ }
+ (*Index) ++;
+ }
+ if (CheckEnd (BasicLineNo, StatementNo, Index))
+ return (FALSE);
+ if (**Index != '=')
+ {
+ BADTOKEN ("\"=\"", TokenMap[**Index].Token);
+ return (FALSE);
+ }
+ (*Index) ++;
+ if (CheckEnd (BasicLineNo, StatementNo, Index))
+ return (FALSE);
+ return (ScanExpression (BasicLineNo, StatementNo, Keyword, Index, &Type, 0)); /* Find an expression */
+}
+
+bool CheckSyntax (int BasicLineNo, byte *Line)
+
+/**********************************************************************************************************************************/
+/* Pre : `Line' points to the converted BASIC line. An initial syntax check has been done already - */
+/* - The line number makes sense; */
+/* - Keywords are at the beginning of each statement and not within a statement; */
+/* - There are less than 128 statements in the line; */
+/* - Brackets match on a per-line basis (but not necessarily on a per-statement basis!) */
+/* - Quotes match; */
+/* Post : The line has been checked against 'normal' Spectrum BASIC syntax. Extended devices that change the normal syntax */
+/* (such as Interface 1 or disk interfaces) are not understood and will generate error messages. */
+/* Import: None. */
+/**********************************************************************************************************************************/
+
+{
+ byte StrippedLine[MAXLINELENGTH + 1];
+ byte *StrippedIndex;
+ byte Keyword;
+ bool AllOk = TRUE;
+ bool VarType;
+ int StatementNo = 0;
+ int ClassIndex = -1;
+
+ StrippedIndex = &(StrippedLine[0]);
+ while (*Line != 0x0D) /* First clean up the line, dropping number expansions and trash */
+ {
+ switch (*Line)
+ {
+ case 0 :
+ case 1 :
+ case 2 :
+ case 3 :
+ case 4 :
+ case 5 :
+ case 6 :
+ case 7 :
+ case 8 :
+ case 9 :
+ case 10 :
+ case 11 :
+ case 12 :
+ case 13 : break;
+ case 14 : *(StrippedIndex ++) = *Line; Line += 5; break; /* EXCEPTION: keep the marker, but drop the number */
+ case 15 : break;
+ case 16 :
+ case 17 :
+ case 18 :
+ case 19 :
+ case 20 :
+ case 21 : Line ++; break;
+ case 22 :
+ case 23 : Line += 2; break;
+ case 24 :
+ case 25 :
+ case 26 :
+ case 27 :
+ case 28 :
+ case 29 :
+ case 30 :
+ case 31 :
+ case 32 : break; /* (We don't care for spaces either!) */
+ default : *(StrippedIndex ++) = *Line; break; /* Pass on only 'good' bits */
+ }
+ Line ++;
+ }
+ *(StrippedIndex ++) = 0x0D;
+ *StrippedIndex = '\0';
+ StrippedIndex = &(StrippedLine[0]); /* Ok, here goes... */
+ while (AllOk && *StrippedIndex != 0x0D) /* Handle each statement */
+ {
+ StatementNo ++;
+ Keyword = *(StrippedIndex ++);
+ if (Keyword == 0xEA) /* 'REM' ? */
+ return (TRUE); /* Then we're done checking this line */
+ if (TokenMap[Keyword].TokenType != 0 && TokenMap[Keyword].TokenType != 1 && TokenMap[Keyword].TokenType != 2) /* (Sanity) */
+ {
+ if (Keyword == 0xA9) /* EXCEPTION: POINT may be used as command */
+ {
+ if (*StrippedIndex != '#') /* It must be followed by a stream in that case */
+ {
+ fprintf (ErrStream, "ERROR - Keyword (\"%s\") error in line %d, statement %d\n",
+ TokenMap[Keyword].Token, BasicLineNo, StatementNo);
+ return (FALSE);
+ }
+ StrippedIndex ++;
+ if (!ScanStream (BasicLineNo, StatementNo, Keyword, &StrippedIndex)) /* (Also signals Interface1/Opus specificness) */
+ return (FALSE);
+ if (*StrippedIndex != ';')
+ {
+ BADTOKEN ("\";\"", TokenMap[*StrippedIndex].Token);
+ return (FALSE);
+ }
+ StrippedIndex ++;
+ if (!HandleClass06 (BasicLineNo, StatementNo, Keyword, &StrippedIndex))
+ return (FALSE);
+ }
+ else
+ {
+ fprintf (ErrStream, "ERROR - Keyword (\"%s\") error in line %d, statement %d\n",
+ TokenMap[Keyword].Token, BasicLineNo, StatementNo);
+ return (FALSE);
+ }
+ }
+ else
+ {
+ ClassIndex = -1;
+#ifdef __DEBUG__
+ RecurseLevel = 0;
+ ListSpaces[0] = '\0';
+ printf ("DEBUG - Start Line %d, Statement %d, Keyword \"%s\"\n", BasicLineNo, StatementNo, TokenMap[Keyword].Token);
+#endif
+ if ((Keyword == 0xE1 || Keyword == 0xF0) && *StrippedIndex == '#') /* EXCEPTION: LIST and LLIST may take a stream */
+ {
+ StrippedIndex ++;
+ if (!ScanStream (BasicLineNo, StatementNo, Keyword, &StrippedIndex)) /* (Also signals Interface1/Opus specificness) */
+ return (FALSE);
+ if (*StrippedIndex != ':' && *StrippedIndex != 0x0D) /* Line number is not required */
+ {
+ if (*StrippedIndex != ',')
+ {
+ BADTOKEN ("\",\"", TokenMap[*StrippedIndex].Token);
+ return (FALSE);
+ }
+ StrippedIndex ++;
+ }
+ }
+ while (AllOk && TokenMap[Keyword].KeywordClass[++ ClassIndex]) /* Handle all class parameters */
+ {
+ if (*StrippedIndex == 0x0D)
+ {
+ if (TokenMap[Keyword].KeywordClass[ClassIndex] != 3 && /* Class 5 and 3 need 0 or more arguments */
+ TokenMap[Keyword].KeywordClass[ClassIndex] != 5)
+ {
+ if ((Keyword == 0xEB && TokenMap[Keyword].KeywordClass[ClassIndex] == 0xCD) || /* 'FOR' doesn't need 'STEP' parameter */
+ (Keyword == 0xFC && TokenMap[Keyword].KeywordClass[ClassIndex] == ',')) /* 'DRAW' doesn't need a third parameter */
+ ClassIndex ++;
+ else
+ {
+ fprintf (ErrStream, "ERROR in line %d, statement %d - Unexpected end of line\n", BasicLineNo, StatementNo);
+ AllOk = FALSE;
+ }
+ }
+ }
+ else if (TokenMap[Keyword].KeywordClass[ClassIndex] >= 32) /* Required token or class ? */
+ {
+ if (*StrippedIndex != TokenMap[Keyword].KeywordClass[ClassIndex]) /* (Required token) */
+ {
+ if ((Keyword == 0xEB && TokenMap[Keyword].KeywordClass[ClassIndex] == 0xCD && *StrippedIndex == ':') ||
+ (Keyword == 0xFC && TokenMap[Keyword].KeywordClass[ClassIndex] == ',' && *StrippedIndex == ':'))
+ ClassIndex ++; /* EXCEPTION: 'FOR' does not require the 'STEP' parameter */
+ /* EXCEPTION: 'DRAW' does not require the third parameter */
+ else
+ { /* (Token not there) */
+ fprintf (ErrStream, "ERROR in line %d, statement %d - Expected \"%s\", but got \"%s\"\n",
+ BasicLineNo, StatementNo, TokenMap[TokenMap[Keyword].KeywordClass[ClassIndex]].Token,
+ TokenMap[*StrippedIndex].Token);
+ AllOk = FALSE;
+ }
+ }
+ else
+ StrippedIndex ++;
+ }
+ else /* (Command class) */
+ switch (TokenMap[Keyword].KeywordClass[ClassIndex])
+ {
+ case 1 : AllOk = HandleClass01 (BasicLineNo, StatementNo, Keyword, &StrippedIndex, &VarType); break;
+ case 2 : AllOk = HandleClass02 (BasicLineNo, StatementNo, Keyword, &StrippedIndex, VarType); break;
+ case 3 : AllOk = HandleClass03 (BasicLineNo, StatementNo, Keyword, &StrippedIndex); break;
+ case 4 : AllOk = HandleClass04 (BasicLineNo, StatementNo, Keyword, &StrippedIndex); break;
+ case 5 : AllOk = HandleClass05 (BasicLineNo, StatementNo, Keyword, &StrippedIndex); break;
+ case 6 : AllOk = HandleClass06 (BasicLineNo, StatementNo, Keyword, &StrippedIndex); break;
+ case 7 : AllOk = HandleClass07 (BasicLineNo, StatementNo, Keyword, &StrippedIndex); break;
+ case 8 : AllOk = HandleClass08 (BasicLineNo, StatementNo, Keyword, &StrippedIndex); break;
+ case 9 : AllOk = HandleClass09 (BasicLineNo, StatementNo, Keyword, &StrippedIndex); break;
+ case 10 : AllOk = HandleClass10 (BasicLineNo, StatementNo, Keyword, &StrippedIndex); break;
+ case 11 : AllOk = HandleClass11 (BasicLineNo, StatementNo, Keyword, &StrippedIndex); break;
+ case 13 : AllOk = HandleClass13 (BasicLineNo, StatementNo, Keyword, &StrippedIndex); break;
+ case 14 : AllOk = HandleClass14 (BasicLineNo, StatementNo, Keyword, &StrippedIndex); break;
+ case 15 : AllOk = HandleClass15 (BasicLineNo, StatementNo, Keyword, &StrippedIndex); break;
+ }
+ }
+ }
+ if (AllOk && Keyword != 0xFA) /* Handling 'IF' and AllOk (i.e. just read the "THEN" ?) */
+ { /* (Nope, go check end of statement) */
+ if (*StrippedIndex != ':' && *StrippedIndex != 0x0D)
+ {
+ if (Keyword == 0xFB && *StrippedIndex == '#') /* EXCEPTION: 'CLS #' is allowed */
+ {
+ StrippedIndex ++;
+ if (!SignalInterface1 (BasicLineNo, StatementNo, 0))
+ return (FALSE);
+ if (*StrippedIndex != ':' && *StrippedIndex != 0x0D)
+ {
+ fprintf (ErrStream, "ERROR in line %d, statement %d - Expected end of statement, but got \"%s\"\n",
+ BasicLineNo, StatementNo, TokenMap[*StrippedIndex].Token);
+ AllOk = FALSE;
+ }
+ }
+ else
+ {
+ fprintf (ErrStream, "ERROR in line %d, statement %d - Expected end of statement, but got \"%s\"\n",
+ BasicLineNo, StatementNo, TokenMap[*StrippedIndex].Token);
+ AllOk = FALSE;
+ }
+ }
+ }
+ if (AllOk && *StrippedIndex == ':') /* (Placing this check here allows weird (but legal) construction "THEN :") */
+ {
+ StrippedIndex ++;
+ while (*StrippedIndex == ':') /* (More consecutive ':' separators are allowed) */
+ {
+ StrippedIndex ++;
+ StatementNo ++;
+ }
+ }
+ }
+ return (AllOk);
+}
+
+int main (int argc, char **argv)
+
+/**********************************************************************************************************************************/
+/* >>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>> MAIN PROGRAM <<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<< */
+/* Import: MatchToken, HandleNumbers, ExpandSequences, PrepareLine, CheckSyntax. */
+/**********************************************************************************************************************************/
+
+{
+ FILE *FpIn;
+ FILE *FpOut;
+ char FileNameIn[256] = "\0";
+ char FileNameOut[256] = "\0";
+ char LineIn[MAXLINELENGTH + 1]; /* One line read from the ASCII file */
+ char *BasicIndex; /* Current scan position in the (converted) ASCII line */
+ byte *ResultIndex; /* Current write index in to the binary result line */
+ byte Token;
+ int LineCount = 0; /* Line count in the ASCII file */
+ int BasicLineNo; /* Current BASIC line number */
+ int SubLineCount; /* Current statement number */
+ bool ExpectKeyword; /* If TRUE, the next scanned token must be a keyword */
+ bool InString; /* TRUE while inside quotes */
+ int BracketCount = 0; /* Match opening and closing brackets */
+ int AutoStart; /* Auto-start line as provided on the command line */
+ int ObjectLength; /* Binary length of one converted line */
+ int BlockSize = 0; /* Total size of the TAP block */
+ byte Parity = 0; /* Overall block parity */
+ bool AllOk = TRUE;
+ bool EndOfFile = FALSE;
+ bool WriteError = FALSE; /* Fingers crossed that this stays FALSE... */
+ size_t Size;
+ int Cnt;
+
+ ErrStream = stderr;
+ Cnt = 1;
+ for (Cnt = 1 ; Cnt < argc && AllOk; Cnt ++) /* Do all command line arguments */
+ {
+ if (argv[Cnt][0] == '-')
+ switch (tolower (argv[Cnt][1]))
+ {
+ case 'c' : CaseIndependant = TRUE; break;
+ case 'w' : NoWarnings = TRUE; break;
+ case 'q' : Quiet = TRUE; break;
+ case 'n' : DoCheckSyntax = FALSE; break;
+ case 'e' : ErrStream = stdout; break;
+ case 'a' : AutoStart = atoi (argv[Cnt] + 2);
+ if (AutoStart < 0 || AutoStart >= 10000)
+ {
+ fprintf (ErrStream, "Invalid auto-start line number %d\n", AutoStart);
+ exit (1);
+ }
+ TapeHeader.HStartLo = (byte)(AutoStart & 0xFF);
+ TapeHeader.HStartHi = (byte)(AutoStart >> 8);
+ break;
+ case 's' : if (strlen (argv[Cnt] + 2) > 10)
+ {
+ fprintf (ErrStream, "Spectrum blockname too long \"%s\"\n", argv[Cnt] + 2);
+ exit (1);
+ }
+ strncpy (TapeHeader.HName, argv[Cnt] + 2, strlen (argv[Cnt] + 2));
+ break;
+ default : fprintf (ErrStream, "Unknown switch \'%c\'\n", argv[Cnt][1]);
+ }
+ else if (FileNameIn[0] == '\0')
+ strcpy (FileNameIn, argv[Cnt]);
+ else if (FileNameOut[0] == '\0')
+ strcpy (FileNameOut, argv[Cnt]);
+ else
+ AllOk = FALSE;
+ }
+ if (FileNameIn[0] == '\0') /* We do need an input file! */
+ AllOk = FALSE;
+ if (!Quiet || !AllOk)
+ printf ("\nBAS2TAP v2.2 by M. van der Heide of ThunderWare Research Center\n\n");
+ if (!AllOk)
+ {
+ printf ("Usage: BAS2TAP [-q] [-w] [-e] [-c] [-aX] [-sX] FileIn [FileOut]\n");
+ printf (" -q = quiet: no banner, no progress indication\n");
+ printf (" -w = suppress generation of warnings\n");
+ printf (" -e = write errors to stdout in stead of stderr channel\n");
+ printf (" -c = case independant tokens (be careful here!)\n");
+ printf (" -n = disable syntax checking\n");
+ printf (" -a = set auto-start line in BASIC header\n");
+ printf (" -s = set \"filename\" in BASIC header\n");
+ exit (1);
+ }
+ if (FileNameOut[0] == '\0')
+ strcpy (FileNameOut, FileNameIn);
+ Size = strlen (FileNameOut);
+ while (-- Size > 0 && FileNameOut[Size] != '.')
+ ;
+ if (Size == 0) /* No extension ? */
+ strcat (FileNameOut, ".tap");
+ else if (strcmp (FileNameOut + Size, ".tap") && strcmp (FileNameOut + Size, ".TAP"))
+ strcpy (FileNameOut + Size, ".tap");
+ if (!Quiet)
+ printf ("Creating output file %s\n",FileNameOut);
+ if ((FpIn = fopen (FileNameIn, "rt")) == NULL)
+ {
+ perror ("ERROR - Cannot open source file");
+ exit (1);
+ }
+ if ((FpOut = fopen (FileNameOut, "wb")) == NULL)
+ {
+ perror ("ERROR - Cannot create output file");
+ fclose (FpIn);
+ exit (1);
+ }
+ Parity = TapeHeader.Flag2;
+ if (fwrite (&TapeHeader, 1, sizeof (struct TapeHeader_s), FpOut) < sizeof (struct TapeHeader_s))
+ { AllOk = FALSE; WriteError = TRUE; } /* Write dummy header to get space */
+ while (AllOk && !EndOfFile)
+ {
+ if (fgets (LineIn, MAXLINELENGTH + 1, FpIn) != NULL)
+ {
+ LineCount ++;
+ if (strlen (LineIn) >= MAXLINELENGTH)
+ { /* We don't require an end-of-line marker */
+ fprintf (ErrStream, "ERROR - Line %d too long\n", LineCount);
+ AllOk = FALSE;
+ }
+ else if ((BasicLineNo = PrepareLine (LineIn, LineCount, &BasicIndex)) < 0)
+ {
+ if (BasicLineNo == -1) /* (Error) */
+ AllOk = FALSE;
+ else /* (Line should simply be skipped) */
+ ;
+ }
+ else if (BasicLineNo >= 10000)
+ {
+ fprintf (ErrStream, "ERROR - Line number %d is larger than the maximum allowed\n", BasicLineNo);
+ AllOk = FALSE;
+ }
+ else
+ {
+ if (!Quiet)
+ {
+ printf ("\rConverting line %4d -> %4d\r", LineCount, BasicLineNo);
+ fflush (stdout); /* (Force line without end-of-line to be printed) */
+ }
+ InString = FALSE;
+ ExpectKeyword = TRUE;
+ SubLineCount = 1;
+ ResultIndex = ResultingLine + 4; /* Reserve space for line number and length */
+ HandlingDEFFN = FALSE;
+ while (*BasicIndex && AllOk)
+ {
+ if (InString)
+ {
+ if (*BasicIndex == '\"')
+ {
+ InString = FALSE;
+ *(ResultIndex ++) = *(BasicIndex ++);
+ while (*BasicIndex == ' ') /* Skip trailing spaces */
+ BasicIndex ++;
+ }
+ else
+ switch (ExpandSequences (BasicLineNo, &BasicIndex, &ResultIndex, FALSE))
+ {
+ case -1 : AllOk = FALSE; break; /* (Error - already reported) */
+ case 0 : *(ResultIndex ++) = *(BasicIndex ++); break; /* (No expansion made) */
+ case 1 : break;
+ }
+ }
+ else if (*BasicIndex == '\"')
+ {
+ if (ExpectKeyword)
+ {
+ fprintf (ErrStream, "ERROR in line %d, statement %d - Expected keyword but got quote\n", BasicLineNo, SubLineCount);
+ AllOk = FALSE;
+ }
+ else
+ {
+ InString = TRUE;
+ *(ResultIndex ++) = *(BasicIndex ++);
+ }
+ }
+ else if (ExpectKeyword)
+ {
+ switch (MatchToken (BasicLineNo, TRUE, &BasicIndex, &Token))
+ {
+ case -2 : AllOk = FALSE; break; /* (Error - already reported) */
+ case -1 : fprintf (ErrStream, "ERROR in line %d, statement %d - Expected keyword but got token \"%s\"\n",
+ BasicLineNo, SubLineCount, TokenMap[Token].Token); /* (Not keyword) */
+ AllOk = FALSE;
+ break;
+ case 0 : fprintf (ErrStream, "ERROR in line %d, statement %d - Expected keyword but got \"%s\"\n", /* (No match) */
+ BasicLineNo, SubLineCount, TokenMap[(byte)(*BasicIndex)].Token);
+ AllOk = FALSE;
+ break;
+ case 1 : *(ResultIndex ++) = Token; /* (Found keyword) */
+ if (Token != ':') /* Special exception; empty statement */
+ ExpectKeyword = FALSE;
+ if (Token == DEFFN)
+ {
+ HandlingDEFFN = TRUE;
+ InsideDEFFN = FALSE;
+ }
+ if (Token == 0xEA) /* Special exception; REM */
+ while (*BasicIndex) /* Simply copy over the remaining part of the line, */
+ /* disregarding token or number expansions */
+ /* As brackets aren't tested for, the match counting stops here */
+ /* (a closing bracket in a REM statement will not be seen by BASIC) */
+ switch (ExpandSequences (BasicLineNo, &BasicIndex, &ResultIndex, FALSE))
+ {
+ case -1 : AllOk = FALSE; break;
+ case 0 : *(ResultIndex ++) = *(BasicIndex ++); break;
+ case 1 : break;
+ }
+ break;
+ }
+ }
+ else if (*BasicIndex == '(') /* Opening bracket */
+ {
+ BracketCount ++;
+ *(ResultIndex ++) = *(BasicIndex ++);
+ if (HandlingDEFFN && !InsideDEFFN)
+#ifdef __DEBUG__
+ {
+ printf ("DEBUG - %sDEFFN, Going inside parameter list\n", ListSpaces);
+ InsideDEFFN = TRUE; /* Signal: require special treatment! */
+ }
+#else
+ InsideDEFFN = TRUE; /* Signal: require special treatment! */
+#endif
+ }
+ else if (*BasicIndex == ')') /* Closing bracket */
+ {
+ if (HandlingDEFFN && InsideDEFFN)
+ {
+#ifdef __DEBUG__
+ printf ("DEBUG - %sDEFFN, Done parameter list\n", ListSpaces);
+ InsideDEFFN = TRUE; /* Signal: require special treatment! */
+#endif
+ *(ResultIndex ++) = 0x0E; /* Insert room for the evaluator (call by value) */
+ *(ResultIndex ++) = 0x00;
+ *(ResultIndex ++) = 0x00;
+ *(ResultIndex ++) = 0x00;
+ *(ResultIndex ++) = 0x00;
+ *(ResultIndex ++) = 0x00;
+ InsideDEFFN = FALSE; /* Mark end of special treatment */
+ HandlingDEFFN = FALSE; /* (The part after the '=' is just like eg. LET) */
+ }
+ if (-- BracketCount < 0) /* More closing than opening brackets */
+ {
+ fprintf (ErrStream, "ERROR in line %d, statement %d - Too many closing brackets\n", BasicLineNo, SubLineCount);
+ AllOk = FALSE;
+ }
+ else
+ *(ResultIndex ++) = *(BasicIndex ++);
+ }
+ else if (*BasicIndex == ',' && HandlingDEFFN && InsideDEFFN)
+ {
+#ifdef __DEBUG__
+ printf ("DEBUG - %sDEFFN, Done parameter; another follows\n", ListSpaces);
+#endif
+ *(ResultIndex ++) = 0x0E; /* Insert room for the evaluator (call by value) */
+ *(ResultIndex ++) = 0x00;
+ *(ResultIndex ++) = 0x00;
+ *(ResultIndex ++) = 0x00;
+ *(ResultIndex ++) = 0x00;
+ *(ResultIndex ++) = 0x00;
+ *(ResultIndex ++) = *(BasicIndex ++); /* (Copy over the ',') */
+ }
+ else
+ switch (MatchToken (BasicLineNo, FALSE, &BasicIndex, &Token))
+ {
+ case -2 : AllOk = FALSE; break; /* (Error - already reported) */
+ case -1 : fprintf (ErrStream, "ERROR in line %d, statement %d - Unexpected keyword \"%s\"\n",/* (Match but keyword) */
+ BasicLineNo, SubLineCount, TokenMap[Token].Token);
+ AllOk = FALSE;
+ break;
+ case 0 : switch (HandleNumbers (BasicLineNo, &BasicIndex, &ResultIndex)) /* (No token) */
+ {
+ case 0 : switch (ExpandSequences (BasicLineNo, &BasicIndex, &ResultIndex, TRUE)) /* (No number) */
+ {
+ case -1 : AllOk = FALSE; break; /* (Error - already reported) */
+ case 0 : if (isalpha (*BasicIndex)) /* (No expansion made) */
+ while (isalnum (*BasicIndex)) /* Skip full strings in one go */
+ *(ResultIndex ++) = *(BasicIndex ++);
+ else
+ *(ResultIndex ++) = *(BasicIndex ++);
+ break;
+ case 1 : break;
+ }
+ break;
+ case -1 : AllOk = FALSE; break;
+ }
+ break;
+ case 1 : *(ResultIndex ++) = Token; /* (Found token, no keyword) */
+ if (Token == ':' || Token == 0xCB)
+ {
+ ExpectKeyword = TRUE;
+ HandlingDEFFN = FALSE;
+ if (BracketCount != 0) /* All brackets match ? */
+ {
+ fprintf (ErrStream, "ERROR in line %d, statement %d - Too few closing brackets\n",
+ BasicLineNo, SubLineCount);
+ AllOk = FALSE;
+ }
+ if (++ SubLineCount > 127)
+ {
+ fprintf (ErrStream, "ERROR - Line %d has too many statements\n", BasicLineNo);
+ AllOk = FALSE;
+ }
+ }
+ break;
+ }
+ }
+ *(ResultIndex ++) = 0x0D;
+ if (AllOk && BracketCount != 0) /* All brackets match ? */
+ {
+ fprintf (ErrStream, "ERROR in line %d, statement %d - Too few closing brackets\n", BasicLineNo, SubLineCount);
+ AllOk = FALSE;
+ }
+ if (AllOk && DoCheckSyntax)
+ AllOk = CheckSyntax (BasicLineNo, ResultingLine + 4); /* Check the syntax of the decoded line */
+ if (AllOk)
+ {
+ ObjectLength = (int)(ResultIndex - ResultingLine);
+ ResultingLine[0] = (byte)(BasicLineNo >> 8); /* Line number is put reversed */
+ ResultingLine[1] = (byte)(BasicLineNo & 0xFF);
+ ResultingLine[2] = (byte)((ObjectLength - 4) & 0xFF); /* Make sure this runs on any CPU */
+ ResultingLine[3] = (byte)((ObjectLength - 4) >> 8);
+ BlockSize += ObjectLength;
+ for (Cnt = 0 ; Cnt < ObjectLength ; Cnt ++)
+ Parity ^= ResultingLine[Cnt];
+ if (BlockSize > 41500) /* (= 65368-23755-<some work/stack space>) */
+ {
+ fprintf (ErrStream, "ERROR - Object file too large at line %d!\n", BasicLineNo);
+ AllOk = FALSE;
+ }
+ else
+ if (fwrite (ResultingLine, 1, ObjectLength, FpOut) != ObjectLength)
+ { AllOk = FALSE; WriteError = TRUE; }
+ }
+ }
+ }
+ else
+ EndOfFile = TRUE;
+ }
+ if (!Quiet)
+ {
+ printf ("\r \r");
+ fflush (stdout);
+ }
+ if (!WriteError) /* Finish the TAP file no matter what went wrong, unless it was the writing itself */
+ {
+ ResultingLine[0] = Parity; /* Now it's time to write the 'real' header in front */
+ if (fwrite (ResultingLine, 1, 1, FpOut) < 1)
+ {
+ perror ("ERROR - Write error");
+ fclose (FpIn);
+ fclose (FpOut);
+ exit (1);
+ }
+ TapeHeader.HLenLo = TapeHeader.HBasLenLo = (byte)(BlockSize & 0xFF);
+ TapeHeader.HLenHi = TapeHeader.HBasLenHi = (byte)(BlockSize >> 8);
+ TapeHeader.LenLo2 = (byte)((BlockSize + 2) & 0xFF);
+ TapeHeader.LenHi2 = (byte)((BlockSize + 2) >> 8);
+ Parity = 0;
+ for (Cnt = 2 ; Cnt < 20 ; Cnt ++)
+ Parity ^= *((byte *)&TapeHeader + Cnt);
+ TapeHeader.Parity1 = Parity;
+ fseek (FpOut, 0, SEEK_SET);
+ if (fwrite (&TapeHeader, 1, sizeof (struct TapeHeader_s), FpOut) < sizeof (struct TapeHeader_s))
+ {
+ perror ("ERROR - Write error");
+ exit (1);
+ }
+ if (!Quiet)
+ {
+ if (AllOk)
+ printf ("Done! Listing contains %d %s.\n", LineCount, LineCount == 1 ? "line" : "lines");
+ else
+ printf ("Listing as far as done contains %d %s.\n", LineCount - 1, LineCount == 2 ? "line" : "lines");
+ if (Is48KProgram >= 0)
+ printf ("Note: this program can only be used in %dK mode\n", Is48KProgram ? 48 : 128);
+ switch (UsesInterface1)
+ {
+ case -1 : break; /* Neither of them */
+ case 0 : printf ("Note: this program requires Interface 1 or Opus Discovery\n"); break;
+ case 1 : printf ("Note: this program requires Interface 1\n"); break;
+ case 2 : printf ("Note: this program requires an Opus Discovery"); break;
+ }
+ }
+ }
+ else
+ perror ("ERROR - Write error");
+ fclose (FpIn);
+ fclose (FpOut);
+ return (AllOk ? 0 : 1); /* (Keep weird compilers happy) */
+}
diff --git a/bas2tap.doc b/bas2tap.doc
new file mode 100644
index 0000000..942c748
--- /dev/null
+++ b/bas2tap.doc
@@ -0,0 +1,186 @@
+BAS2TAP v2.2 (Release 13-06-2003)
+Written by M. van der Heide of ThunderWare Research Center
+
+This program converts 'BASIC in an ASCII file' to a TAP tape image file, to be
+used in a ZX Spectrum emulator. Both 48K and 128K listings are supported.
+
+
+STANDARD DISCLAIMER
+-------------------
+This program is provided "as is", without any warranties, nor an indication
+that it will fit the purpose.
+BAS2TAP has been tested on several systems and OSes, causing no problems.
+If you want to use this program, though, you do so at your own risk.
+This means that it is extremely unlikely to damage your system, but if the
+unthoughtful event DOES happen, you will be on your own.
+ThunderWare Research Center can and will accept no claims whatsoever.
+
+This program is neither public domain nor shareware: it's Open Source Software.
+This means that anyone is allowed to distribute and use the program, as well as
+change the source code. The source code (modified or not) must be included in
+any distribution at all time.
+You can not request any money from anyone for copying this program, apart from
+the expenses made for the media onto which it has been copied.
+The package contents may not be changed when distributing it. At least the
+following files must be present:
+
+ BAS2TAP.C source text file
+ BAS2TAP.DOC this documentation file
+
+Remember you can always freely obtain the latest version from
+the utilities section of The World of Spectrum, at:
+ http://www.worldofspectrum.org/
+
+Or you can try:
+ ftp://ftp.worldofspectrum.org/pub/sinclair/tools/pc
+
+
+REVISION HISTORY
+----------------
+1.0 26-03-98: First public release
+1.1 01-04-98: Changes:
+ - Added banner.
+ - Switch -q does not suppress warnings, but progress indication.
+ - Added switch -w to suppress warnings.
+ - Added switch -e on popular demand.
+ - Empty lines are skipped and only generate a warning.
+ - Lines with only a line number generate an error.
+ - Numbers can also start with a decimal point (eg. '.5').
+ - Lines no longer require an end-of-line terminator.
+ - In-line sequence expansions also remove any trailing
+ spaces unless it was between quotes.
+ - The created TAP is made valid (although incomplete) if an error
+ occurs, unless it was a write error.
+1.2 11-12-98: Changes:
+ - Block graphics were encoded rotated by 1.
+ - Added full syntax checking, and
+ - Added switch -n to disable the syntax checking again.
+ - Added expansion sequences '{CAT}' and '{CODE}' with the sole
+ purpose of being used as channel name in the OPEN # command.
+ - Changed the distribution policy from freeware to Open Source
+ Software.
+1.3 04-01-99: Changes:
+ - Spaces after strings are now skipped (they are not needed).
+ - The 'CLS #' command does exist on the Interface 1 after all.
+ - Exponential numbers were raised the wrong way around
+ (eg. 25e3 became 0.025 and 25e-3 became 25000). Oooops....
+1.4 17-05-99: Changes:
+ - UDGs used as 'USR "x"' would result in a crash due to 2
+ bad pointers.
+ - Complex expressions between brackets as parameter to other
+ expression types (eg. ATTR or string slicing) would screw
+ up the evaluation.
+ - The 'CLS #' command was flagged as being Interface 1
+ specific in the previous version rather than either
+ Interface 1 or Opus Discovery (aaargh!)
+1.5 19-05-99: Changes:
+ - The 'DIM' command's syntax description was incorrect, so the
+ checker didn't like the variable name before the opening
+ bracket.
+1.6 23-06-99: Changes:
+ - Several problems with array indices!
+ - The 'USR' token only accepted a numerical operand. An
+ exception was made for UDG expressions '"x"' but constructions
+ like 'USR CHR$ a' didn't work.
+1.7 02-11-99: Changes:
+ - The DEF FN function requires insertion of evaluation room.
+1.8 09-11-00: Changes:
+ - The DEF FN function only requires insertion of evaluation room
+ in the parameter area before the '=' token.
+ Any bracketed argument would upset the parser.
+1.9 08-12-01: Changes:
+ - Forgot to skip the ';' colour token separator in class 9.
+ - REM statements should be parsed as-is (except for expansion
+ sequences), without taking out multiple spaces or counting
+ quotes.
+2.0 11-12-02: Changes:
+ - SAVE/LOAD/VERIFY/MERGE DATA didn't work, as the DATA token was
+ set to be allowed as keyword only.
+ - ERASE ! didn't work, as ERASE was only allowed from the "m"
+ chennel.
+2.1 22-12-02: Changes:
+ - Any alphanumerical expression can be sliced, not only
+ variables and direct strings.
+ - If the output filename provided on the command line had the
+ extension '.TAP', it shouldn't be forced to '.tap'.
+2.2 13-06-03: Changes:
+ - Expand sequences PAPER and INK had their codes reversed.
+ - Expand sequences BRIGHT and FLASH had their codes reversed.
+ - Expand sequences INVERSE and OVER had their codes reversed.
+ - Expand sequences {XX} didn't work (missing pointer advance).
+ - Expand sequences AT and TAB didn't work and threw a
+ warning instead (missing pointer advance).
+
+SYNTAX
+------
+ BAS2TAP [-q] [-w] [-e] [-c] [-aX] [-sX] FileIn [FileOut]
+
+-q goes into quiet mode, suppressing banner and progress indication.
+-w suppress generation of warnings.
+-e write errors to the stdout in stead of the stderr stream.
+-c makes token matching case independant. The drawback is that variables that
+ have the same name as a token (eg. 'print') cannot be used, as they will be
+ treated as (unexpected) tokens.
+-n disables syntax checking.
+-aX sets the auto-start line to line X.
+-sX sets the TAP block name to X (max 10 characters).
+
+If no output filename is provided, the same name as the input file will be
+used, but with extension `.TAP'.
+
+
+THE CONVERTOR
+-------------
+The following rules apply:
+
+1. One BASIC line must be provided in one ASCII line.
+2. Keywords must appear in upper case (unless switch -c is used).
+3. Keywords that consist of multiple words (eg. `GO TO') must have the space
+ in between the words.
+4. Spaces between words are not needed, unless this leads to concatenation
+ problems - eg. `PRINTVAL"10"' must appear as `PRINT VAL"10"', while
+ `PRINT10' is perfectly acceptable.
+5. Syntax is fully checked according to
+ - the normal Spectrum ROM,
+ - Interface 1/Microdrive extension,
+ - Opus Discovery extension.
+
+The following sequences are expanded in-line:
+
+ Chars Expand to
+
+ tab character 06 ("print '")
+ {XX} XX is 2-digit hex, converts to the Spectrum ASCII value
+ {(C)} character 7F (copyright sign)
+ {-X} X is 1-8, characters 80-87 (block graphics without shift)
+ {+X} X is 1-8, characters 88-8F (block graphics with shift)
+ {X} X is `A'-`U', converts to the UDG Spectrum ASCII value
+ {CODE} character AF ('CODE')
+ {CAT} character CF ('CAT')
+
+Special sequences are (case independant) PAPER, INK, BRIGHT, FLASH, OVER,
+INVERSE, AT and TAB control sequences. They may appear as "{PAPER 3}" to convert
+to the sequence 0x10,0x03. Note that both AT and TAB take 2 operands, which
+should be seperated by a comma (eg. "{AT 1,10}").
+Notice that these expansions are identical to what SGD and TAPER create when
+saving out BASIC blocks.
+
+
+RECOMPILING THE SOURCE
+----------------------
+The source is provided in (almost) fully portable ANSI C, the source text is
+written in 132 columns.
+
+If you wish to experiment with different syntax (eg. for a DISCiPLE), you may
+find it useful to set the `__DEBUG__' define.
+
+If you make any fundamental change to the source, you are requested - but not
+obligated - to send the changed source back to me, so that everyone can benefit
+from it. My e-mail address is `mheide@worldofspectrum.org'
+
+
+Enjoy!
+
+Martijn van der Heide, MSc
+Senior Software Developer
+ThunderWare Research Center
diff --git a/game.bas b/game.bas
new file mode 100644
index 0000000..1757efb
--- /dev/null
+++ b/game.bas
@@ -0,0 +1,10 @@
+10 REM Guess a number
+20 CLS : RANDOMIZE
+30 LET n = INT(RND * 8) +1 : LET t = 1
+100 INPUT "Guess a number from 1 to 9", g
+120 PRINT "Your guess is "; g
+130 IF g < n THEN PRINT "The number is bigger"
+140 IF g > n THEN PRINT "The number is smaller"
+150 IF g = n THEN GO TO 200
+160 LET t = t + 1 : GO TO 100
+200 PRINT "You won in "; t ; " turns"