/**********************************************************************************************************************************/
/* Module : BAS2TAP.C */
/* Executable : BAS2TAP.EXE */
/* Doc file : BAS2TAP.DOC */
/* Version type : Single file */
/* Last changed : 24-07-2005 16:00 */
/* Update count : 16 */
/* 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-2005 by Martijn 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", 6, { 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 HandleBIN (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 HandleBIN (int BasicLineNo, char **BasicLine, byte **SpectrumLine)
/**********************************************************************************************************************************/
/* Pre : `BasicLineNo' holds the current BASIC line number, `BasicLine' points into the line just past the BIN token, */
/* `SpectrumLine' points to the TAPped Spectrum line. */
/* Post : If there was a BINary number at this position, it has been processed into `SpectrumLine' and `LineIndex' is pointing */
/* after the number. */
/* The return value is: 1 = number done, -1 = number error (already reported). */
/* Import: None. */
/**********************************************************************************************************************************/
{
int Value = 0;
while ((**BasicLine) == '0' || (**BasicLine) == '1') /* Read only binary digits */
{
Value = Value * 2 + **BasicLine - '0';
if (Value > 65535)
{
fprintf (ErrStream, "ERROR - Number too big in line %d\n", BasicLineNo);
return (-1);
}
*((*SpectrumLine) ++) = *((*BasicLine) ++); /* (Copy digit across) */
}
*((*SpectrumLine) ++) = 0x0E; /* Insert number marker */
*((*SpectrumLine) ++) = 0x00; /* (A small integer by definition) */
*((*SpectrumLine) ++) = 0x00;
*((*SpectrumLine) ++) = (byte)(Value & 0xFF);
*((*SpectrumLine) ++) = (byte)(Value >> 8);
*((*SpectrumLine) ++) = 0x00;
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 == '.' || **Index == 0xC4) /* 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
if (Keyword == 0xA8 || Keyword == 0xE9) /* Do not parse any bracketing if checking DIM or FN */
ParseArray = -1;
else if (Keyword == 0xF1) /* LET is allowed to write to a substring */
ParseArray = 1;
else
ParseArray = 2;
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;
#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.4 by Martijn 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;
}
}
else if (Token == 0xC4) /* BIN */
{
if (HandleBIN (BasicLineNo, &BasicIndex, &ResultIndex) == -1)
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 (0); /* (Keep weird compilers happy) */
}
syntax highlighted by Code2HTML, v. 0.9.1