diff options
author | Juan J. Martinez <jjm@usebox.net> | 2023-07-29 08:12:16 +0100 |
---|---|---|
committer | Juan J. Martinez <jjm@usebox.net> | 2023-07-29 08:25:22 +0100 |
commit | f569f70654def8f2b590fca62dbfa3e7b2b5dd8c (patch) | |
tree | ea33632775a75e9ac1caf3f12cbf09763f30dcc3 | |
download | sinclair-basic-main.tar.gz sinclair-basic-main.zip |
-rw-r--r-- | .gitignore | 5 | ||||
-rw-r--r-- | Makefile | 26 | ||||
-rw-r--r-- | README.md | 25 | ||||
-rw-r--r-- | bas2tap.c | 3286 | ||||
-rw-r--r-- | bas2tap.doc | 186 | ||||
-rw-r--r-- | game.bas | 10 |
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" |