-- BasicScanner.mesa
-- edited by Brotz and Hilton, September 23, 1982 5:14 PM

DIRECTORY
Ascii,
BasicDefs,
BasicImpDefs,
Real,
RealOps,
Storage,
String;

BasicScanner: PROGRAM
IMPORTS BasicImpDefs, Real, RealOps, Storage, String
EXPORTS BasicImpDefs =

BEGIN OPEN BasicDefs, BasicImpDefs;

charTableArray: CharTable;
charTablePtr: PUBLIC POINTER TO CharTable ← @charTableArray;

inputLine: PUBLIC STRING ← [200];
inputLineIndex: PUBLIC CARDINAL ← 0;
token: PUBLIC STRING ← [40];
haveToken: PUBLIC BOOLEAN ← FALSE;
endOfLine: PUBLIC BOOLEAN ← TRUE;

optionBase: PUBLIC CARDINAL ← 0;
optionBaseCalled: PUBLIC BOOLEAN ← FALSE;
-- optionBaseCalled will be set to TRUE when the first DIM statement is called
-- or when OPTION BASE is called

realPosOrNeg: PUBLIC PositiveOrNegative ← positive;
-- Sets the sign for real numbers. Is set to negative in Primary and reset
-- to positive in GetRealNumber.

inputToken: STRING ← [40];
userInputLine: PUBLIC STRING ← [200];
userInputLineIndex: PUBLIC CARDINAL ← 0;
noMoreInput: PUBLIC BOOLEAN ← FALSE;

statementSeparatorChar: PUBLIC CHARACTER ← ’:;


AppendTailOfRealNumber: PROCEDURE [realString: STRING] =
-- Ends having gotten the token following the tail of a real number.
BEGIN
IF GetToken[] AND token[0] IN [’0 .. ’9] THEN
BEGIN
String.AppendString[realString, token];
IF GetToken[] AND String.LowerCase[token[0]] = ’e THEN
BEGIN
String.AppendChar[realString, ’E];
IF GetToken[] AND token[0] = ’+ OR token[0] = ’- THEN
BEGIN
String.AppendString[realString, token];
IF GetToken[] AND token[0] IN [’0 .. ’9] THEN
{String.AppendString[realString, token]; [] ← GetToken[]}
ELSE ParseError["Missing exponential factor in real number representation."L];
END
ELSE ParseError["Missing +/- in real number representation."L];
END;
END
ELSE ParseError["Illegal real number representation after decimal point."L];
END; -- of GetTailOfRealNumber --


CheckForEqualSign: PUBLIC PROCEDURE =
BEGIN
IF GetToken[] AND token[0] = ’= THEN RETURN
ELSE ParseError["Missing = within statement."L];
END; -- of CheckForEqualSign --


CheckForVariable: PUBLIC PROCEDURE =
BEGIN
IF GetToken[] AND charTableArray[token[0]] = letter
AND ~IsReservedWord[token] THEN RETURN
ELSE ParseError["Missing variable within statement."L];
END; -- of CheckForVariable --


FindInfixReservedWord: PUBLIC PROCEDURE [word: STRING]
RETURNS [wordStart: CARDINAL] =
-- Starting at inputLineIndex, finds the index in inputLine, "wordStart", at which the
-- reserved word "word" begins. Use this procedure to find where an infix reserved word
-- such as THEN, ELSE, TO, STEP, etc. begins so as to fake the GetToken procedure into
-- terminating tokens before such a word.
BEGIN
wordIndex: CARDINAL ← 0;
i: CARDINAL;
IF word.length = 0 THEN ERROR;
wordStart ← 0;
FOR i ← inputLineIndex, i + 1 UNTIL i >= inputLine.length DO
SELECT inputLine[i] FROM
’" => BEGIN
i ← i + 1;
UNTIL i >= inputLine.length DO
IF inputLine[i] = ’" THEN
{IF i + 1 = inputLine.length OR inputLine[i + 1] # ’" THEN EXIT ELSE i ← i + 1};
i ← i + 1;
ENDLOOP;
wordIndex ← 0;
END;
Ascii.SP, Ascii.TAB => LOOP;
statementSeparatorChar => RETURN[0];
String.UpperCase[word[wordIndex]], String.LowerCase[word[wordIndex]] =>
BEGIN
IF wordIndex = 0 THEN wordStart ← i;
wordIndex ← wordIndex + 1;
IF wordIndex = word.length THEN RETURN;
END;
ENDCASE => IF wordIndex > 0 THEN {i ← wordStart; wordIndex ← 0};
ENDLOOP;
END; -- of FindInfixReservedWord --


GetInputValue: PUBLIC PROCEDURE [isString: BOOLEAN] RETURNS [value: BasicValue] =
BEGIN
start, end, i: CARDINAL;
string: STRING;
hasDot: BOOLEAN ← FALSE;
FOR start ← inputLineIndex, start + 1 UNTIL start >= inputLine.length DO
SELECT inputLine[start] FROM
Ascii.SP, Ascii.TAB => NULL;
ENDCASE => EXIT;
ENDLOOP;
inputLineIndex ← start;
IF start >= inputLine.length THEN
BEGIN
IF isString THEN value ← BasicValue[string, string[stringValue: Storage.String[0]]]
ELSE value ← BasicValue[integer, integer[integerValue: 0]];
RETURN;
END;
IF inputLine[start] = ’" THEN
BEGIN
IF ~isString THEN RunTimeError["Quoted string not allowed for this input!"L];
start ← end ← start + 1;
UNTIL end >= inputLine.length DO
IF inputLine[end] = ’" THEN
BEGIN
end ← end + 1;
IF end = inputLine.length OR inputLine[end] # ’" THEN
{inputLineIndex ← end; end ← end - 1; EXIT};
END;
end ← end + 1;
REPEAT
FINISHED => RunTimeError["Input string missing right quote!"L];
ENDLOOP;
END
ELSE BEGIN
FOR end ← start, end + 1 UNTIL end >= inputLine.length DO
SELECT inputLine[end] FROM
Ascii.SP, Ascii.TAB => EXIT;
ENDCASE;
ENDLOOP;
inputLineIndex ← end;
END;
string ← Storage.String[end - start];
FOR i ← start, i + 1 UNTIL i >= end DO
String.AppendChar[string, inputLine[i]];
SELECT inputLine[i] FROM
’" => i ← i + 1;
’. => hasDot ← TRUE;
ENDCASE;
ENDLOOP;
SELECT TRUE FROM
isString => {value ← BasicValue[string, string[stringValue: string]]; RETURN};
hasDot => value ← BasicValue[real, real[realValue: Real.StringToReal[string]]];
ENDCASE => value ← BasicValue[integer, integer
[integerValue: String.StringToLongNumber[string, 10]]];
Storage.FreeString[string];
END; -- of GetInputValue --


GetRealNumber: PUBLIC PROCEDURE [integerPart: STRING] RETURNS [realNum: REAL] =
-- Ends having gotten the next token after the complete real number.
BEGIN
realString: STRING ← [40];
IF realPosOrNeg = negative THEN
{String.AppendChar[realString, ’-]; realPosOrNeg ← positive};
IF integerPart[0] ~IN [’0 .. ’9] THEN ParseError["Illegal integer part in real number."L];
String.AppendString[realString, integerPart];
String.AppendChar[realString, ’.];
AppendTailOfRealNumber[realString];
realNum ← Real.StringToReal[realString];
END; -- of GetRealNumber --


GetSubstring: PUBLIC PROCEDURE [varPtr: VariablePtr] RETURNS [value: BasicValue] =
-- This procedure has not been updated --
BEGIN
-- startIndex: CARDINAL ← 0;
-- endIndex: CARDINAL;
-- [startIndex, endIndex] ← GetSubstringIndex[varPtr];
-- IF token[0] = ’] THEN value ← ExtractSubstring[varPtr, startIndex, endIndex];
END; -- of GetSubstring --


GetSubstringIndex: PUBLIC PROCEDURE [varPtr: VariablePtr]
RETURNS [startIndex, endIndex: CARDINAL] =
BEGIN
-- This procedure has not been updated --
-- Assumes that the left bracket has already been GetToken’ed.
subscript: BasicValue;
IF GetToken[] THEN
BEGIN
-- subscript ← Expression[];
SELECT subscript.type FROM
integer => startIndex ← NumericToDecimal[subscript.integerValue];
real => startIndex ← RealOps.RoundC[subscript.realValue];
ENDCASE => BEGIN
RunTimeError["Substring subscripts must have a numeric value."L];
Storage.FreeString[subscript.stringValue];
RETURN;
END;
IF token[0] = ’, THEN
BEGIN -- There are two subscripts. Get the second one.
IF GetToken[] THEN
BEGIN
-- subscript ← Expression[];
SELECT subscript.type FROM
integer => startIndex ← NumericToDecimal[subscript.integerValue];
real => startIndex ← RealOps.RoundC[subscript.realValue];
ENDCASE =>
BEGIN
RunTimeError["Substring subscripts must have a numeric value."L];
Storage.FreeString[subscript.stringValue];
RETURN;
END;
END
ELSE ParseError["Missing substring index."L];
END
ELSE endIndex ← WITH v: varPtr SELECT FROM
string => v.value.stringValue.length
ENDCASE => ERROR;
IF ~haveToken OR token[0] # ’] THEN ParseError["Missing right bracket."L];
END
ELSE ParseError["Missing array index."L];
END; -- of GetSubstringIndex --


GetToken: PUBLIC PROCEDURE RETURNS [b: BOOLEAN] =
BEGIN
firstCharIsDigit: BOOLEAN;

AppendCharAndBump: PROCEDURE =
BEGIN
String.AppendChar[token, inputLine[inputLineIndex]];
inputLineIndex ← inputLineIndex + 1;
END; -- of AppendCharAndBump --

token.length ← 0;
endOfLine ← FALSE;
haveToken ← TRUE;
DO
IF inputLineIndex = inputLine.length THEN
{endOfLine ← TRUE; RETURN[(haveToken ← (token.length # 0))]};
SELECT charTableArray[inputLine[inputLineIndex]] FROM
letter =>
BEGIN
IF token.length = 0 THEN firstCharIsDigit ← FALSE;
IF firstCharIsDigit = TRUE THEN RETURN[TRUE];
AppendCharAndBump[];
IF IsReservedWord[token] THEN RETURN[TRUE];
END;
digit => {IF token.length = 0 THEN firstCharIsDigit ← TRUE; AppendCharAndBump[]};
statementSeparator =>
BEGIN
IF token.length = 0 THEN {AppendCharAndBump[]; RETURN[(haveToken ← FALSE)]};
RETURN[TRUE];
END;
punctuation => {IF token.length = 0 THEN AppendCharAndBump[]; RETURN[TRUE]};
illegal =>
BEGIN
ParseError["Illegal characer found while parsing!"L];
endOfLine ← TRUE;
RETURN[(haveToken ← FALSE)];
END;
white => inputLineIndex ← inputLineIndex + 1;
ENDCASE;
ENDLOOP;
END; -- of GetToken --


InitCharTable: PUBLIC PROCEDURE =
BEGIN
ch: CHARACTER;
i: CARDINAL;
whiteString: STRING = "

"L;
punctuationString: STRING = "!#%~&*()-`=+\|[{]}←↑;:’"",<.>/?"L;
FOR ch IN [0C..177C] DO
charTableArray[ch] ← illegal;
ENDLOOP;
FOR ch IN [’0..’9] DO
charTableArray[ch] ← digit;
ENDLOOP;
FOR ch IN [’A..’Z] DO
charTableArray[ch] ← letter;
ENDLOOP;
FOR ch IN [’a..’z] DO
charTableArray[ch] ← letter;
ENDLOOP;
charTableArray[’$] ← letter;
FOR i IN [0..whiteString.length) DO
charTableArray[whiteString[i]] ← white;
ENDLOOP;
FOR i IN [0..punctuationString.length) DO
charTableArray[punctuationString[i]] ← punctuation;
ENDLOOP;
charTableArray[statementSeparatorChar] ← statementSeparator;
END; -- of InitCharTable --



NumericToDecimal: PUBLIC PROCEDURE [number: Numeric]
RETURNS [decimal: CARDINAL] =
BEGIN
decimalString: STRING ← [20];
decimalString.length ← 0;
String.AppendLongDecimal[decimalString, number];
decimal ← String.StringToDecimal[decimalString];
END; -- of NumericToDecimal --


ParseSubscripts: PUBLIC PROCEDURE RETURNS [nIndices: CARDINAL] =
-- Parses one or two subscripts and returns how many it found.
BEGIN
-- Assumes that the left parenthesis or bracket has already been GetToken’ed.
isBracket: BOOLEAN = (token[0] = ’[);
nIndices ← 0;
IF GetToken[] THEN
BEGIN
Expression[];
nIndices ← 1;
IF token[0] = ’, THEN
BEGIN -- There are two subscripts. Get the second one.
IF GetToken[] THEN {Expression[]; nIndices ← 2}
ELSE ParseError["Missing subscript!"L];
END;
IF ~haveToken OR (isBracket AND token[0] # ’]) OR (~isBracket AND token[0] # ’)) THEN
ParseError
[IF isBracket THEN "Missing right bracket!"L ELSE "Missing right parenthesis!"L];
END
ELSE ParseError["Missing subscript!"L];
END; -- of ParseSubscripts --


ScanQuotedString: PUBLIC PROCEDURE RETURNS [quoteString: STRING] =
-- Allocates and returns a string containing the contents of inputLine from inputLineIndex
-- up to a terminating quote. Two double quote marks in a row are reduced to a single
-- double quote mark in the returned string.
BEGIN
startIndex: CARDINAL ← inputLineIndex;
i: CARDINAL;
UNTIL inputLineIndex = inputLine.length DO
IF inputLine[inputLineIndex] = ’" THEN
BEGIN
inputLineIndex ← inputLineIndex + 1;
IF inputLineIndex = inputLine.length OR inputLine[inputLineIndex] # ’" THEN EXIT;
-- inputLine[inputLineIndex - 2] is the last good char in quoted string.
END;
inputLineIndex ← inputLineIndex + 1;
REPEAT
FINISHED => BEGIN
-- inputLineIndex became equal to inputLine.length before we saw the terminating quote.
ParseError["The string expression is missing a matching "" ."L];
RETURN[Storage.String[0]];
END;
ENDLOOP;
quoteString ← Storage.String[inputLineIndex - startIndex - 1];
FOR i ← startIndex, i + 1 UNTIL i = inputLineIndex - 1 DO
String.AppendChar[quoteString, inputLine[i]];
IF inputLine[i] = ’" THEN i ← i + 1;
ENDLOOP;
END; -- of ScanQuotedString --


END. -- of BasicScanner --