-- Last edited by Barth, December 6, 1981  5:36 PM
-- Last edited by Chi Yung, February 25, 1982  4:33 PM
-- Last edited by Chi Yung, March 30, 1982  2:13 PM
-- Last edited by Chi Yung, April 27, 1982  6:57 PM
-- Last edited by Chi Yung, April 30, 1982  2:37 PM
-- Last edited by Chi Yung, September 1, 1983  11:31 AM
-- Last edited by JWHITE, 25-Apr-85 12:41:54

DIRECTORY
  Ascii,
  MachineParseDefs,
  String,
  InlineDefs: FROM "inlinedefs",
  vmD: FROM "VirtualMgrDefs";

MachineParse: PROGRAM
  IMPORTS String, vmD, InlineDefs
  EXPORTS MachineParseDefs =
BEGIN

OPEN MachineParseDefs;

ParseFault: PUBLIC ERROR [why:STRING, pfBegin:vmD.CharIndex,
  pfEnd:vmD.CharIndex] = CODE;

ParseMessage: PUBLIC PROCEDURE [ph:ParseHandle,
  fields:DESCRIPTOR FOR ARRAY OF FieldRec]=
BEGIN
  word: STRING ← [MaxFieldNameLength];
  fieldName: STRING ← [MaxFieldNameLength];
  fieldBegin: vmD.CharIndex;
  ph.currentChar ← 0;
  [] ← ParseToLeadingChar[ph];
  -- loop invariant is final CR of previous field has just been eaten
  UNTIL vmD.GetMessageSize[ph.message]=ph.currentChar DO
    fieldName.length ← 0;
    fieldBegin ← ph.currentChar;
-- InlineDefs.BITAND  is a procedure that performs an AND function between 
-- the 8 bits correspond to the character obtained by the      
-- vmD.GetMessageChar[ph.message,ph.currentChar] with the character 
-- correspond to 177C which is 01111111 (8 bits) in binary. This AND action 
-- will change the bits patent of the character in such a way that the 
-- first bit is always 0. The rest of the bits are the same. In this way we 
-- will only read in the character we put in. Those pseudo characters, with 
-- the first bit in their binary form which is not 0, put 
-- in automatically by the laurel,such as the character corresponds to the 
-- auto line feed, will not be read.
    UNTIL InlineDefs.BITAND[vmD.GetMessageChar[ph.message,ph.currentChar], 
    177C]=': DO
      IF ~ParseToLeadingChar[ph] THEN
      IF ph.currentChar=vmD.GetMessageSize[ph.message] THEN GOTO
      endOfMessage;
      ParseWord[ph,word];
      IF word[0]=': AND fieldName.length = 0 THEN ERROR 
      ParseFault["Null field name illegal",
      fieldBegin, ph.currentChar];
      IF word[0]=': AND fieldName.length # 0 THEN BEGIN
       ph.currentChar ← ph.currentChar-word.length;
       word.length ← 0;
       END;
      IF fieldName.length + word.length > fieldName.maxlength THEN ERROR
      ParseFault["fieldName too long!", ph.currentChar, ph.currentChar]; 
      String.AppendString[fieldName, word];
      IF vmD.GetMessageSize[ph.message]=ph.currentChar THEN
      ERROR ParseFault["Field name has no terminating colon", fieldBegin,
      ph.currentChar];
    ENDLOOP;
    ph.currentChar ← ph.currentChar+1; -- eat colon
    FOR i:CARDINAL IN [0..LENGTH[fields]) DO
      IF String.EquivalentString[fieldName, fields[i].name] THEN BEGIN
        IF fields[i].found THEN
        ERROR ParseFault["Multiply defined field illegal",
        fieldBegin, ph.currentChar];
        fields[i].found ← TRUE;
        fields[i].parseProc[ph];
        EXIT;
      END;
    REPEAT
    FINISHED => DO
      IF ph.currentChar=vmD.GetMessageSize[ph.message]
      THEN GOTO endOfMessage;
      IF InlineDefs.BITAND[vmD.GetMessageChar[ph.message,ph.currentChar], 
      177C]=Ascii.CR THEN BEGIN
        ph.currentChar ← ph.currentChar+1;
        IF ph.currentChar=vmD.GetMessageSize[ph.message]
        THEN GOTO endOfMessage;
        IF ~IsBlank[ph, vmD.GetMessageChar[ph.message, ph.currentChar]]
        THEN EXIT;
      END;
      ph.currentChar ← ph.currentChar+1;
    ENDLOOP;
    ENDLOOP;
  REPEAT
    endOfMessage => NULL;
  ENDLOOP;
  FOR i:CARDINAL IN [0..LENGTH[fields]) DO
    IF fields[i].mustExist AND ~fields[i].found THEN BEGIN
      errorString: STRING ← [MaxFieldNameLength+20];
      String.AppendString[errorString, "Field "];
      String.AppendString[errorString, fields[i].name];
      String.AppendString[errorString, " is missing"];
      ERROR ParseFault[errorString, 0, 0];
    END;
  ENDLOOP;
END; -- of ParseMessage

IsBlank: PUBLIC PROCEDURE [ph:ParseHandle, c:CHARACTER] RETURNS [BOOLEAN]=
BEGIN
  FOR i:CARDINAL IN [0..ph.blankSet.length) DO
    IF c=ph.blankSet[i] THEN RETURN[TRUE];
  ENDLOOP;
  RETURN[FALSE];
END; -- of IsBlank

IsBreak: PUBLIC PROCEDURE [ph:ParseHandle, c:CHARACTER] RETURNS [BOOLEAN]=
BEGIN
  FOR i:CARDINAL IN [0..ph.breakSet.length) DO
    IF c=ph.breakSet[i] THEN RETURN[TRUE];
  ENDLOOP;
  RETURN[FALSE];
END; -- of IsBreak

CheckChar: PUBLIC PROCEDURE[ph:ParseHandle, c:CHARACTER]=
BEGIN
  ErrorString: STRING ← [100];
  IF String.UpperCase[ParseChar[ph]]#c THEN BEGIN
    ErrorString.length ← 0;
    String.AppendString[ErrorString, "Selected char doesn't match expected char which should be "];
    String.AppendChar[ErrorString, c];
    ERROR ParseFault[ErrorString, ph.currentChar-1, ph.currentChar-1];
  END;
END; -- of CheckChar

ParseChar: PUBLIC PROCEDURE[ph:ParseHandle] RETURNS [c:CHARACTER]=
BEGIN
  IF ph.currentChar=vmD.GetMessageSize[ph.message] THEN
  ERROR ParseFault["Field ends too soon",ph.currentChar,ph.currentChar];
  c ← InlineDefs.BITAND[vmD.GetMessageChar[ph.message,ph.currentChar], 
      177C];
  ph.currentChar ← ph.currentChar+1;
END; -- of ParseChar

ParseCharIfCharIs: PUBLIC PROCEDURE[ph:ParseHandle, c:CHARACTER]
RETURNS [BOOLEAN]=
BEGIN
  IF ph.currentChar=vmD.GetMessageSize[ph.message] THEN RETURN[FALSE];
  IF c=String.UpperCase[InlineDefs.BITAND
  [vmD.GetMessageChar[ph.message,ph.currentChar],177C]]
  THEN {ph.currentChar ← ph.currentChar+1; RETURN[TRUE]};
  RETURN[FALSE];
END; -- of ParseCharIfCharIs

ParseToEndOfField: PUBLIC PROCEDURE[ph:ParseHandle]=
BEGIN
  curPos:vmD.CharIndex ← ph.currentChar;
  IF ParseToLeadingChar[ph] THEN
  ERROR ParseFault
  ["Selected text not recognised", ph.currentChar, ph.currentChar];
END; -- of ParseToEndOfField

ParseToEndOfLine: PUBLIC PROCEDURE[ph:ParseHandle]=
BEGIN
  curPos:vmD.CharIndex ← ph.currentChar;
  IF ParseToLeadingCharOnLine[ph] THEN
  ERROR ParseFault["Selected text not recognised", curPos, ph.currentChar];
END; -- of ParseToEndOfLine

ParseToNonWhite: PUBLIC PROCEDURE[ph:ParseHandle]=
BEGIN
  IF ~ParseToLeadingCharOnLine[ph] THEN
  ERROR ParseFault["Line ends too soon",ph.currentChar,ph.currentChar];
END; -- of ParseToNonWhite

ParseToLeadingChar: PUBLIC PROCEDURE[ph:ParseHandle]
RETURNS[isThere: BOOLEAN]=
BEGIN
  c:CHARACTER;
  DO
    IF ParseToLeadingCharOnLine[ph] THEN RETURN[TRUE];
    IF ph.currentChar=vmD.GetMessageSize[ph.message] THEN RETURN[FALSE];
    c ← InlineDefs.BITAND[vmD.GetMessageChar[ph.message,ph.currentChar], 
        177C];
    WHILE c=Ascii.CR DO
      ph.currentChar ← ph.currentChar+1;
      IF ph.currentChar=vmD.GetMessageSize[ph.message] THEN RETURN[FALSE];
      c ← InlineDefs.BITAND[vmD.GetMessageChar[ph.message,ph.currentChar], 
          177C];
      IF c= '! THEN EXIT;
      IF ~IsBlank[ph, c] THEN RETURN[FALSE];
    ENDLOOP;
    IF c= '! THEN LOOP;
    IF ~IsBlank[ph, c] THEN RETURN[TRUE];
    ph.currentChar ← ph.currentChar+1;
  ENDLOOP;
END; -- of ParseToLeadingChar

ParseToLeadingCharOnLine: PUBLIC PROCEDURE[ph:ParseHandle]
  RETURNS[isThere: BOOLEAN]=
BEGIN
  c:CHARACTER;
  DO
    IF ph.currentChar=vmD.GetMessageSize[ph.message] THEN RETURN[FALSE];
    c ← InlineDefs.BITAND[vmD.GetMessageChar[ph.message,ph.currentChar], 
        177C];
    IF c='! THEN DO
      ph.currentChar ← ph.currentChar+1;
      IF ph.currentChar=vmD.GetMessageSize[ph.message] THEN RETURN[FALSE];
      c ← InlineDefs.BITAND[vmD.GetMessageChar[ph.message,ph.currentChar], 
          177C];
      IF c=Ascii.CR THEN RETURN[FALSE];
    ENDLOOP;
    IF c=Ascii.CR THEN RETURN[FALSE];
    IF ~IsBlank[ph, c] THEN RETURN[TRUE];
    ph.currentChar ← ph.currentChar+1;
  ENDLOOP;
END; -- of ParseToLeadingCharOnLine

ParseWord: PUBLIC PROCEDURE[ph:ParseHandle, s:STRING]=
BEGIN
  c:CHARACTER;
  ParseToNonWhite[ph];
  s.length ← 0;
  DO
    c ← InlineDefs.BITAND[vmD.GetMessageChar[ph.message,ph.currentChar], 
        177C];
    IF IsBlank[ph, c] OR IsBreak[ph, c] THEN EXIT;
    IF s.length=s.maxlength THEN ERROR ParseFault["Word too long",
    ph.currentChar-s.length, ph.currentChar];
    s[s.length] ← c;
    s.length ← s.length+1;
    ph.currentChar ← ph.currentChar+1;
    IF ph.currentChar=vmD.GetMessageSize[ph.message] THEN EXIT;
  ENDLOOP;
  IF s.length=0 THEN BEGIN
    s[0] ← c;
    s.length ← 1;
    ph.currentChar ← ph.currentChar+1;
  END;
END; -- of ParseWord

ParseDecimal: PUBLIC PROCEDURE [ph:ParseHandle, lBound:CARDINAL ← 0,
uBound:CARDINAL ← LAST[CARDINAL]] RETURNS [n:CARDINAL]=
BEGIN
  s:STRING ← [8];
  OutOfBoundString: STRING ← [100];
  ParseWord[ph, s];
  DO
      IF s[s.length-1] ~ IN ['0..'9] THEN BEGIN
        s.length ← s.length -1;
        ph.currentChar ← ph.currentChar - 1;
      END ELSE EXIT;
   ENDLOOP;
   n ← String.StringToNumber[s,10 ! String.InvalidNumber =>
    ParseFault["Invalid number", ph.currentChar-s.length, ph.currentChar]];
  IF n<lBound  THEN BEGIN
    String.AppendString
    [OutOfBoundString,  "Number is out of the lower bound, which is "];
    String.AppendNumber[OutOfBoundString, lBound , 10];
    ParseFault[OutOfBoundString, ph.currentChar-s.length, ph.currentChar];
  END;
  IF n>uBound THEN BEGIN
    String.AppendString
    [OutOfBoundString,  "Number is out of the upper bound, which is "];
    String.AppendNumber[OutOfBoundString, uBound , 10];
    ParseFault[OutOfBoundString,  ph.currentChar-s.length, ph.currentChar];
  END;
END; -- of ParseDecimal


ParseInteger: PUBLIC PROCEDURE [ph:ParseHandle, lBound:INTEGER ← FIRST[INTEGER], uBound:INTEGER ← LAST[INTEGER]] RETURNS [n:INTEGER]=
BEGIN
  s:STRING ← [8];
  OutOfBoundString: STRING ← [100];
  ParseWord[ph, s];
  DO
      IF s[s.length-1] ~ IN ['0..'9] THEN BEGIN
        s.length ← s.length -1;
        ph.currentChar ← ph.currentChar - 1;
      END ELSE EXIT;
   ENDLOOP;
   n ← String.StringToNumber[s,10 ! String.InvalidNumber =>
    ParseFault["Invalid number", ph.currentChar-s.length, ph.currentChar]];
  IF n<lBound  THEN BEGIN
    String.AppendString
    [OutOfBoundString,  "Number is out of the lower bound, which is "];
    String.AppendNumber[OutOfBoundString, lBound , 10];
    ParseFault[OutOfBoundString, ph.currentChar-s.length, ph.currentChar];
  END;
  IF n>uBound THEN BEGIN
    String.AppendString
    [OutOfBoundString,  "Number is out of the upper bound, which is "];
    String.AppendNumber[OutOfBoundString, uBound , 10];
    ParseFault[OutOfBoundString,  ph.currentChar-s.length, ph.currentChar];
  END;
END; -- of ParseINTEGER

ParseScaledNumber:PUBLIC PROCEDURE[ph:ParseHandle,scale:INTEGER ← 0,
lBound:CARDINAL ← 0,  uBound:CARDINAL ← LAST[CARDINAL]] RETURNS [n:CARDINAL ← 0, NumberOfDigitsAndDecimal: CARDINAL ← 0]=
BEGIN
  c:CHARACTER;
  decimal: INTEGER← 0;
  --decimal is the number of digits after the decimal point + the 
  --decimal point
  decimalLoophole: CARDINAL ← 0;
  nlong:LONG CARDINAL ← 0;
  OutOfBoundString: STRING ← [100];
  AccuracyString: STRING ← [100];
  ParseToNonWhite[ph];
  IF ParseCharIfCharIs[ph, '-] THEN ERROR ParseFault
  ["Negative number is not allowed",ph.currentChar -1, ph.currentChar];
  DO
    c ← InlineDefs.BITAND[vmD.GetMessageChar[ph.message,ph.currentChar], 
        177C];
    IF c ~IN ['0..'9] THEN EXIT;
    nlong ← nlong*10 +(c -'0);
    NumberOfDigitsAndDecimal ← NumberOfDigitsAndDecimal +1;
    ph.currentChar ← ph.currentChar+1;
    IF ph.currentChar=vmD.GetMessageSize[ph.message] THEN EXIT;
  ENDLOOP;
  IF ParseCharIfCharIs[ph, '.] THEN  BEGIN
    NumberOfDigitsAndDecimal ← NumberOfDigitsAndDecimal +1;
    decimal ← decimal + 1;
    DO
      c ← InlineDefs.BITAND[vmD.GetMessageChar[ph.message,ph.currentChar], 
          177C];
      IF c ~IN ['0..'9] THEN EXIT;
      nlong ← nlong*10 +(c -'0);
      NumberOfDigitsAndDecimal ← NumberOfDigitsAndDecimal +1;
      decimal ← decimal + 1;
      ph.currentChar ← ph.currentChar+1;
      IF ph.currentChar=vmD.GetMessageSize[ph.message] THEN EXIT;
    ENDLOOP;
  END;
  IF (decimal - 1)  > scale THEN BEGIN
    String.AppendString[AccuracyString,  
    "Number of decimal places allowed is "];
    String.AppendNumber[AccuracyString, scale, 10];
    ERROR ParseFault[AccuracyString, 
    ph.currentChar - NumberOfDigitsAndDecimal,ph.currentChar];
  END;
  decimalLoophole ← LOOPHOLE [decimal];
  IF decimal = 0 AND NumberOfDigitsAndDecimal  > 4 OR
  decimal # 0 AND NumberOfDigitsAndDecimal - decimalLoophole  > 4  
  THEN ERROR ParseFault
  ["You are trying to enter an unreasonably large number.",  
  ph.currentChar -NumberOfDigitsAndDecimal, ph.currentChar];
  -- The reason that the above tests is here instead of earlier 
  -- in the procedure is becuase we need to finish the do loop in 
  -- order to obtain the value of the NumberOfDigitsAndDecimal so 
  -- as to display the error message correctly.
  IF decimal = 0 THEN decimal ← decimal + 1;
  SELECT (decimal-1)-scale FROM
    > 0 =>  THROUGH[1..(decimal-1)-scale]  DO
              nlong ← nlong/10
            ENDLOOP;
    < 0 =>  THROUGH[1..scale-(decimal-1)] DO
              nlong ← nlong*10
            ENDLOOP;
  ENDCASE;
  IF nlong<lBound  THEN BEGIN
    String.AppendString[OutOfBoundString,  
    "Number is out of the lower bound, which is "];
    AppendScaleNumber[lBound, scale, OutOfBoundString];
    ParseFault[OutOfBoundString, ph.currentChar-NumberOfDigitsAndDecimal , 
    ph.currentChar];
  END;
  IF nlong>uBound THEN BEGIN
    String.AppendString[OutOfBoundString,  
    "Number is out of the upper bound, which is "];
    AppendScaleNumber[uBound, scale, OutOfBoundString];
    ParseFault[OutOfBoundString,  ph.currentChar-NumberOfDigitsAndDecimal, 
    ph.currentChar];
  END;
  n ← InlineDefs.LowHalf[nlong];
END; --  of ParseScaledNumberNoSpace

AppendScaleNumber: PUBLIC PROCEDURE[n: CARDINAL, scale: CARDINAL, 
OutOfBoundString:STRING]=
BEGIN
  m: CARDINAL;
  m ← n;
  THROUGH [1..scale] DO
    m ← m/10
  ENDLOOP;
  String.AppendNumber[OutOfBoundString, m, 10];
  String.AppendChar[OutOfBoundString,'.];
  THROUGH [1..scale] DO
    m ← m*10
  ENDLOOP;
  m ← n - m;
  String.AppendNumber[OutOfBoundString, m, 10];
END;

MissingValueCheck: PUBLIC PROCEDURE[ph:ParseHandle] =
BEGIN
  c: CHARACTER;
  curPos: vmD.CharIndex;
  ParseToNonWhite[ph];
  curPos ← ph.currentChar;
  c ← ParseChar[ph];
  IF c = '- THEN ERROR ParseFault
  ["Cannot have negative number!", curPos, curPos+ 1];
  IF c = '. THEN BEGIN
    ParseToNonWhite[ph];
    c  ← ParseChar[ph];
    IF c ~ IN ['0..'9] THEN ParseFault["Value is missing here.", curPos,
    curPos+1];
    ph.currentChar ← curPos;
  END ELSE BEGIN
    IF c ~ IN ['0..'9] THEN ERROR ParseFault["Value is missing here.",
    curPos-1, curPos-1];
    ph.currentChar ← curPos;
  END;
END; -- of MissingValueCheck

ParseNibble: PUBLIC PROCEDURE [ph:ParseHandle, lBound:CARDINAL,
uBound:CARDINAL, nullOK:BOOLEAN ← TRUE] RETURNS [n:Nibble]=
BEGIN
  c:CHARACTER;
  s:STRING ← [8];
  ParseWord[ph, s];
  n ← 0;
  FOR i:CARDINAL IN [0..s.length) DO
    c ← s[i];
    IF c >= '0 AND c <= '9 THEN n ← n*16 + (c - '0)
    ELSE BEGIN
      c←String.UpperCase[c];
      IF c >= 'A AND c <= 'F THEN n ← n*16 + (c - 'A + 10)
      ELSE ERROR ParseFault["Invalid hex digit in number",
      ph.currentChar-s.length, ph.currentChar];
    END;
  ENDLOOP;
  IF (n<lBound OR n>uBound) AND (~nullOK OR n#0)
  THEN ParseFault["Hex number out of bounds",
  ph.currentChar-s.length, ph.currentChar];
END; -- of ParseNibble

ParseBinary: PUBLIC PROCEDURE[ph:ParseHandle] RETURNS[n:CARDINAL]=
BEGIN
  c:CHARACTER;
  ParseToNonWhite[ph];
  c ← InlineDefs.BITAND[vmD.GetMessageChar[ph.message,ph.currentChar], 
      177C];
  ph.currentChar ← ph.currentChar+1;
  IF c='0 THEN RETURN[0];
  IF c='1 THEN RETURN[1];
  ERROR ParseFault["Illegal binary value",ph.currentChar,ph.currentChar+1];
END; -- of ParseBinary

ParseTime: PUBLIC PROCEDURE[ph:ParseHandle] RETURNS[h,m,s:CARDINAL]=
BEGIN
  h ← ParseDecimal[ph,0,99]; CheckChar[ph, ':];
  m ← ParseDecimal[ph,0,59]; CheckChar[ph, ':];
  s ← ParseDecimal[ph,0,59];
END; -- of ParseTime

END.