(* mcLexBuf.mod provides a buffer for the all the tokens created by m2.lex.
Copyright (C) 2015-2023 Free Software Foundation, Inc.
Contributed by Gaius Mulley <gaius.mulley@southwales.ac.uk>.
This file is part of GNU Modula-2.
GNU Modula-2 is free software; you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation; either version 3, or (at your option)
any later version.
GNU Modula-2 is distributed in the hope that it will be useful, but
WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
General Public License for more details.
You should have received a copy of the GNU General Public License
along with GNU Modula-2; see the file COPYING3.  If not see
<http://www.gnu.org/licenses/>.  *)
IMPLEMENTATION MODULE mcLexBuf ;
IMPORT mcflex ;
FROM libc IMPORT strlen ;
FROM SYSTEM IMPORT ADDRESS ;
FROM Storage IMPORT ALLOCATE, DEALLOCATE ;
FROM DynamicStrings IMPORT string, InitString, InitStringCharStar, Equal, Mark, KillString ;
FROM FormatStrings IMPORT Sprintf1 ;
FROM nameKey IMPORT NulName, Name, makekey, keyToCharStar ;
FROM mcReserved IMPORT toktype ;
FROM mcComment IMPORT isProcedureComment, isBodyComment, isAfterComment, getContent ;
FROM mcPrintf IMPORT printf0, printf1, printf2, printf3 ;
FROM mcDebug IMPORT assert ;
CONST
   MaxBucketSize = 100 ;
   Debugging     = FALSE ;
TYPE
   sourceList = POINTER TO RECORD
                              left,
                              right: sourceList ;
                              name : String ;
                              line : CARDINAL ;
                              col  : CARDINAL ;
                           END ;
   tokenDesc = RECORD
                  token: toktype ;
                  str  : Name ;
                  int  : INTEGER ;
		  com  : commentDesc ;
                  line : CARDINAL ;
                  col  : CARDINAL ;
                  file : sourceList ;
               END ;
   tokenBucket = POINTER TO RECORD
                               buf : ARRAY [0..MaxBucketSize] OF tokenDesc ;
                               len : CARDINAL ;
                               next: tokenBucket ;
                            END ;
   listDesc = RECORD
                 head,
                 tail            : tokenBucket ;
                 lastBucketOffset: CARDINAL ;
              END ;
VAR
   procedureComment,
   bodyComment,
   afterComment     : commentDesc ;
   currentSource    : sourceList ;
   useBufferedTokens,
   currentUsed      : BOOLEAN ;
   listOfTokens     : listDesc ;
   nextTokNo        : CARDINAL ;
(*
   debugLex - display the last, n, tokens.
*)
PROCEDURE debugLex (n: CARDINAL) ;
VAR
   c,
   i, o, t: CARDINAL ;
   b      : tokenBucket ;
BEGIN
   IF nextTokNo > n
   THEN
      o := nextTokNo - n
   ELSE
      o := 0
   END ;
   i := 0 ;
   REPEAT
      t := o + i ;
      IF nextTokNo = t
      THEN
         printf0 ("nextTokNo ")
      END ;
      b := findtokenBucket (t) ;
      IF b = NIL
      THEN
         t := o + i ;
         printf1 ("end of buf  (%d is further ahead than the buffer contents)\n", t)
      ELSE
         c := o + i ;
         printf2 ("entry %d  %d ", c, t) ;
         displayToken (b^.buf[t].token) ;
	 printf0 ("\n") ;
         INC (i)
      END
   UNTIL b = NIL
END debugLex ;
(*
   getProcedureComment - returns the procedure comment if it exists,
                         or NIL otherwise.
*)
PROCEDURE getProcedureComment () : commentDesc ;
BEGIN
   RETURN procedureComment
END getProcedureComment ;
(*
   getBodyComment - returns the body comment if it exists,
                    or NIL otherwise.  The body comment is
                    removed if found.
*)
PROCEDURE getBodyComment () : commentDesc ;
VAR
   b: commentDesc ;
BEGIN
   b := bodyComment ;
   bodyComment := NIL ;
   RETURN b
END getBodyComment ;
(*
   seekTo -
*)
PROCEDURE seekTo (t: CARDINAL) ;
VAR
   b: tokenBucket ;
BEGIN
   nextTokNo := t ;
   IF t > 0
   THEN
      DEC (t) ;
      b := findtokenBucket (t) ;
      IF b = NIL
      THEN
         updateFromBucket (b, t)
      END
   END
END seekTo ;
(*
   peeptokenBucket -
*)
PROCEDURE peeptokenBucket (VAR t: CARDINAL) : tokenBucket ;
VAR
   ct : toktype ;
   old,
   n  : CARDINAL ;
   b, c: tokenBucket ;
BEGIN
   ct := currenttoken ;
   IF Debugging
   THEN
      debugLex (5)
   END ;
   old := getTokenNo () ;
   REPEAT
      n := t ;
      b := findtokenBucket (n) ;
      IF b = NIL
      THEN
         doGetToken ;
         n := t ;
         b := findtokenBucket (n) ;
         IF (b = NIL) OR (currenttoken = eoftok)
         THEN
            (* bailing out.  *)
            nextTokNo := old + 1 ;
            b := findtokenBucket (old) ;
            updateFromBucket (b, old) ;
            RETURN NIL
         END
      END ;
   UNTIL (b # NIL) OR (currenttoken = eoftok) ;
   t := n ;
   nextTokNo := old + 1 ;
   IF Debugging
   THEN
      printf2 ("nextTokNo = %d, old = %d\n", nextTokNo, old)
   END ;
   b := findtokenBucket (old) ;
   IF Debugging
   THEN
      printf1 ("  adjusted old = %d\n", old)
   END ;
   IF b # NIL
   THEN
      updateFromBucket (b, old)
   END ;
   IF Debugging
   THEN
      debugLex (5)
   END ;
   assert (ct = currenttoken) ;
   RETURN b
END peeptokenBucket ;
(*
   peepAfterComment - peeps ahead looking for an after statement comment.  It stops at an END token
                      or if the line number changes.
*)
PROCEDURE peepAfterComment ;
VAR
   oldTokNo,
   t,
   peep,
   cno,
   nextline,
   curline : CARDINAL ;
   b       : tokenBucket ;
   finished: BOOLEAN ;
BEGIN
   oldTokNo := nextTokNo ;
   cno := getTokenNo () ;
   curline := tokenToLineNo (cno, 0) ;
   nextline := curline ;
   peep := 0 ;
   finished := FALSE ;
   REPEAT
      t := cno + peep ;
      b := peeptokenBucket (t) ;
      IF (b = NIL) OR (currenttoken = eoftok)
      THEN
         finished := TRUE
      ELSE
         nextline := b^.buf[t].line ;
	 IF nextline = curline
         THEN
            CASE b^.buf[t].token OF
            eoftok,
            endtok    :  finished := TRUE |
            commenttok:  IF isAfterComment (b^.buf[t].com)
                         THEN
	                    afterComment := b^.buf[t].com
                         END
	    ELSE
	    END
	 ELSE
            finished := TRUE
         END
      END ;
      INC (peep)
   UNTIL finished ;
   seekTo (oldTokNo)
END peepAfterComment ;
(*
   getAfterComment - returns the after comment if it exists,
                     or NIL otherwise.  The after comment is
                     removed if found.
*)
PROCEDURE getAfterComment () : commentDesc ;
VAR
   a: commentDesc ;
BEGIN
   peepAfterComment ;
   a := afterComment ;
   afterComment := NIL ;
   RETURN a
END getAfterComment ;
(*
   init - initializes the token list and source list.
*)
PROCEDURE init ;
BEGIN
   currenttoken := eoftok ;
   nextTokNo := 0 ;
   currentSource := NIL ;
   listOfTokens.head := NIL ;
   listOfTokens.tail := NIL ;
   useBufferedTokens := FALSE ;
   procedureComment := NIL ;
   bodyComment := NIL ;
   afterComment := NIL ;
   lastcomment := NIL
END init ;
(*
   addTo - adds a new element to the end of sourceList, currentSource.
*)
PROCEDURE addTo (l: sourceList) ;
BEGIN
   l^.right := currentSource ;
   l^.left  := currentSource^.left ;
   currentSource^.left^.right := l ;
   currentSource^.left := l ;
   WITH l^.left^ DO
      line := mcflex.getLineNo() ;
      col  := mcflex.getColumnNo()
   END
END addTo ;
(*
   subFrom - subtracts, l, from the source list.
*)
PROCEDURE subFrom (l: sourceList) ;
BEGIN
   l^.left^.right := l^.right ;
   l^.right^.left := l^.left
END subFrom ;
(*
   newElement - returns a new sourceList
*)
PROCEDURE newElement (s: ADDRESS) : sourceList ;
VAR
   l: sourceList ;
BEGIN
   NEW (l) ;
   IF l=NIL
   THEN
      HALT
   ELSE
      WITH l^ DO
         name  := InitStringCharStar (s) ;
         left  := NIL ;
         right := NIL
      END
   END ;
   RETURN l
END newElement ;
(*
   newList - initializes an empty list with the classic dummy header element.
*)
PROCEDURE newList () : sourceList ;
VAR
   l: sourceList ;
BEGIN
   NEW (l) ;
   WITH l^ DO
      left  := l ;
      right := l ;
      name  := NIL
   END ;
   RETURN l
END newList ;
(*
   checkIfNeedToDuplicate - checks to see whether the currentSource has
                            been used, if it has then duplicate the list.
*)
PROCEDURE checkIfNeedToDuplicate ;
VAR
   l, h: sourceList ;
BEGIN
   IF currentUsed
   THEN
      l := currentSource^.right ;
      h := currentSource ;
      currentSource := newList() ;
      WHILE l#h DO
         addTo (newElement (l^.name)) ;
         l := l^.right
      END
   END
END checkIfNeedToDuplicate ;
(*
   pushFile - indicates that, filename, has just been included.
*)
PROCEDURE pushFile (filename: ADDRESS) ;
VAR
   l: sourceList ;
BEGIN
   checkIfNeedToDuplicate ;
   addTo (newElement (filename)) ;
   IF Debugging
   THEN
      IF currentSource^.right#currentSource
      THEN
         l := currentSource ;
         REPEAT
            printf3 ('name = %s, line = %d, col = %d\n', l^.name, l^.line, l^.col) ;
            l := l^.right
         UNTIL l=currentSource
      END
   END
END pushFile ;
(*
   popFile - indicates that we are returning to, filename, having finished
             an include.
*)
PROCEDURE popFile (filename: ADDRESS) ;
VAR
   l: sourceList ;
BEGIN
   checkIfNeedToDuplicate ;
   IF (currentSource#NIL) AND (currentSource^.left#currentSource)
   THEN
      l := currentSource^.left ;  (* last element *)
      subFrom (l) ;
      DISPOSE (l) ;
      IF (currentSource^.left#currentSource) AND
         (NOT Equal(currentSource^.name, Mark (InitStringCharStar (filename))))
      THEN
         (* mismatch in source file names after preprocessing files *)
      END
   ELSE
      (* source file list is empty, cannot pop an include.. *)
   END
END popFile ;
(*
   killList - kills the sourceList providing that it has not been used.
*)
PROCEDURE killList ;
VAR
   l, k: sourceList ;
BEGIN
   IF (NOT currentUsed) AND (currentSource#NIL)
   THEN
      l := currentSource ;
      REPEAT
         k := l ;
         l := l^.right ;
         DISPOSE (k)
      UNTIL l=currentSource
   END
END killList ;
(*
   reInitialize - re-initialize the all the data structures.
*)
PROCEDURE reInitialize ;
VAR
   s, t: tokenBucket ;
BEGIN
   IF listOfTokens.head#NIL
   THEN
      t := listOfTokens.head ;
      REPEAT
         s := t ;
         t := t^.next ;
         DISPOSE (s) ;
      UNTIL t=NIL ;
      currentUsed := FALSE ;
      killList
   END ;
   init
END reInitialize ;
(*
   setFile - sets the current filename to, filename.
*)
PROCEDURE setFile (filename: ADDRESS) ;
BEGIN
   killList ;
   currentUsed   := FALSE ;
   currentSource := newList() ;
   addTo (newElement (filename))
END setFile ;
(*
   openSource - attempts to open the source file, s.
                The success of the operation is returned.
*)
PROCEDURE openSource (s: String) : BOOLEAN ;
BEGIN
   IF useBufferedTokens
   THEN
      getToken ;
      RETURN TRUE
   ELSE
      IF mcflex.openSource (string (s))
      THEN
         setFile (string (s)) ;
         syncOpenWithBuffer ;
         getToken ;
         RETURN TRUE
      ELSE
         RETURN FALSE
      END
   END
END openSource ;
(*
   closeSource - closes the current open file.
*)
PROCEDURE closeSource ;
BEGIN
   IF useBufferedTokens
   THEN
      WHILE currenttoken#eoftok DO
         getToken
      END
   ELSE
      (* a subsequent call to mcflex.OpenSource will really close the file *)
   END
END closeSource ;
(*
   resetForNewPass - reset the buffer pointers to the beginning ready for
                     a new pass
*)
PROCEDURE resetForNewPass ;
BEGIN
   nextTokNo := 0 ;
   useBufferedTokens := TRUE
END resetForNewPass ;
(*
   displayToken -
*)
PROCEDURE displayToken (t: toktype) ;
BEGIN
   CASE t OF
   eoftok: printf0('eoftok\n') |
   plustok: printf0('plustok\n') |
   minustok: printf0('minustok\n') |
   timestok: printf0('timestok\n') |
   dividetok: printf0('dividetok\n') |
   becomestok: printf0('becomestok\n') |
   ambersandtok: printf0('ambersandtok\n') |
   periodtok: printf0('periodtok\n') |
   commatok: printf0('commatok\n') |
   commenttok: printf0('commenttok\n') |
   semicolontok: printf0('semicolontok\n') |
   lparatok: printf0('lparatok\n') |
   rparatok: printf0('rparatok\n') |
   lsbratok: printf0('lsbratok\n') |
   rsbratok: printf0('rsbratok\n') |
   lcbratok: printf0('lcbratok\n') |
   rcbratok: printf0('rcbratok\n') |
   uparrowtok: printf0('uparrowtok\n') |
   singlequotetok: printf0('singlequotetok\n') |
   equaltok: printf0('equaltok\n') |
   hashtok: printf0('hashtok\n') |
   lesstok: printf0('lesstok\n') |
   greatertok: printf0('greatertok\n') |
   lessgreatertok: printf0('lessgreatertok\n') |
   lessequaltok: printf0('lessequaltok\n') |
   greaterequaltok: printf0('greaterequaltok\n') |
   periodperiodtok: printf0('periodperiodtok\n') |
   colontok: printf0('colontok\n') |
   doublequotestok: printf0('doublequotestok\n') |
   bartok: printf0('bartok\n') |
   andtok: printf0('andtok\n') |
   arraytok: printf0('arraytok\n') |
   begintok: printf0('begintok\n') |
   bytok: printf0('bytok\n') |
   casetok: printf0('casetok\n') |
   consttok: printf0('consttok\n') |
   definitiontok: printf0('definitiontok\n') |
   divtok: printf0('divtok\n') |
   dotok: printf0('dotok\n') |
   elsetok: printf0('elsetok\n') |
   elsiftok: printf0('elsiftok\n') |
   endtok: printf0('endtok\n') |
   exittok: printf0('exittok\n') |
   exporttok: printf0('exporttok\n') |
   fortok: printf0('fortok\n') |
   fromtok: printf0('fromtok\n') |
   iftok: printf0('iftok\n') |
   implementationtok: printf0('implementationtok\n') |
   importtok: printf0('importtok\n') |
   intok: printf0('intok\n') |
   looptok: printf0('looptok\n') |
   modtok: printf0('modtok\n') |
   moduletok: printf0('moduletok\n') |
   nottok: printf0('nottok\n') |
   oftok: printf0('oftok\n') |
   ortok: printf0('ortok\n') |
   pointertok: printf0('pointertok\n') |
   proceduretok: printf0('proceduretok\n') |
   qualifiedtok: printf0('qualifiedtok\n') |
   unqualifiedtok: printf0('unqualifiedtok\n') |
   recordtok: printf0('recordtok\n') |
   repeattok: printf0('repeattok\n') |
   returntok: printf0('returntok\n') |
   settok: printf0('settok\n') |
   thentok: printf0('thentok\n') |
   totok: printf0('totok\n') |
   typetok: printf0('typetok\n') |
   untiltok: printf0('untiltok\n') |
   vartok: printf0('vartok\n') |
   whiletok: printf0('whiletok\n') |
   withtok: printf0('withtok\n') |
   asmtok: printf0('asmtok\n') |
   volatiletok: printf0('volatiletok\n') |
   periodperiodperiodtok: printf0('periodperiodperiodtok\n') |
   datetok: printf0('datetok\n') |
   linetok: printf0('linetok\n') |
   filetok: printf0('filetok\n') |
   integertok: printf0('integertok\n') |
   identtok: printf0('identtok\n') |
   realtok: printf0('realtok\n') |
   stringtok: printf0('stringtok\n')
   ELSE
      printf0 ('unknown tok (--fixme--)\n')
   END
END displayToken ;
(*
   updateFromBucket - updates the global variables:  currenttoken,
                      currentstring, currentcolumn and currentinteger
                      from tokenBucket, b, and, offset.
*)
PROCEDURE updateFromBucket (b: tokenBucket; offset: CARDINAL) ;
BEGIN
   WITH b^.buf[offset] DO
      currenttoken   := token ;
      currentstring  := keyToCharStar (str) ;
      currentcolumn  := col ;
      currentinteger := int ;
      currentcomment := com ;
      IF currentcomment # NIL
      THEN
         lastcomment := currentcomment
      END ;
      IF Debugging
      THEN
         printf3 ('line %d (# %d  %d) ', line, offset, nextTokNo)
      END
   END
END updateFromBucket ;
(*
   getToken - gets the next token into currenttoken.
*)
PROCEDURE getToken ;
BEGIN
   REPEAT
      doGetToken ;
      IF currenttoken = commenttok
      THEN
	 IF isProcedureComment (currentcomment)
	 THEN
            procedureComment := currentcomment ;
            bodyComment := NIL ;
            afterComment := NIL ;
	 ELSIF isBodyComment (currentcomment)
	 THEN
            bodyComment := currentcomment ;
            afterComment := NIL
	 ELSIF isAfterComment (currentcomment)
	 THEN
            procedureComment := NIL ;
            bodyComment := NIL ;
            afterComment := currentcomment
	 END
      END
   UNTIL currenttoken # commenttok
END getToken ;
(*
   doGetToken - fetch the next token into currenttoken.
*)
PROCEDURE doGetToken ;
VAR
   a: ADDRESS ;
   t: CARDINAL ;
   b: tokenBucket ;
BEGIN
   IF useBufferedTokens
   THEN
      t := nextTokNo ;
      b := findtokenBucket (t) ;
      updateFromBucket (b, t)
   ELSE
      IF listOfTokens.tail=NIL
      THEN
         a := mcflex.getToken () ;
         IF listOfTokens.tail=NIL
         THEN
            HALT
         END
      END ;
      IF nextTokNo>=listOfTokens.lastBucketOffset
      THEN
         (* nextTokNo is in the last bucket or needs to be read.  *)
         IF nextTokNo-listOfTokens.lastBucketOffset<listOfTokens.tail^.len
         THEN
            IF Debugging
            THEN
               printf0 ('fetching token from buffer (updateFromBucket)\n')
            END ;
            updateFromBucket (listOfTokens.tail,
                              nextTokNo-listOfTokens.lastBucketOffset)
         ELSE
            IF Debugging
            THEN
               printf0 ('calling flex to place token into buffer\n')
            END ;
            (* call the lexical phase to place a new token into the last bucket.  *)
            a := mcflex.getToken () ;
            getToken ; (* and call ourselves again to collect the token from bucket.  *)
            RETURN
         END
      ELSE
         IF Debugging
         THEN
            printf0 ('fetching token from buffer\n')
         END ;
         t := nextTokNo ;
         b := findtokenBucket (t) ;
         updateFromBucket (b, t)
      END
   END ;
   IF Debugging
   THEN
      displayToken (currenttoken)
   END ;
   INC (nextTokNo)
END doGetToken ;
(*
   syncOpenWithBuffer - synchronise the buffer with the start of a file.
                        Skips all the tokens to do with the previous file.
*)
PROCEDURE syncOpenWithBuffer ;
BEGIN
   IF listOfTokens.tail#NIL
   THEN
      WITH listOfTokens.tail^ DO
         nextTokNo := listOfTokens.lastBucketOffset+len
      END
   END
END syncOpenWithBuffer ;
(*
   insertToken - inserts a symbol, token, infront of the current token
                 ready for the next pass.
*)
PROCEDURE insertToken (token: toktype) ;
BEGIN
   IF listOfTokens.tail#NIL
   THEN
      WITH listOfTokens.tail^ DO
         IF len>0
         THEN
            buf[len-1].token := token
         END
      END ;
      addTokToList (currenttoken, NulName, 0, NIL,
                    getLineNo (), getColumnNo (), currentSource) ;
      getToken
   END
END insertToken ;
(*
   insertTokenAndRewind - inserts a symbol, token, infront of the current token
                          and then moves the token stream back onto the inserted token.
*)
PROCEDURE insertTokenAndRewind (token: toktype) ;
BEGIN
   IF listOfTokens.tail#NIL
   THEN
      WITH listOfTokens.tail^ DO
         IF len>0
         THEN
            buf[len-1].token := token
         END
      END ;
      addTokToList (currenttoken, NulName, 0, NIL,
                    getLineNo(), getColumnNo(), currentSource) ;
      currenttoken := token
   END
END insertTokenAndRewind ;
(*
   getPreviousTokenLineNo - returns the line number of the previous token.
*)
PROCEDURE getPreviousTokenLineNo () : CARDINAL ;
BEGIN
   RETURN getLineNo()
END getPreviousTokenLineNo ;
(*
   getLineNo - returns the current line number where the symbol occurs in
               the source file.
*)
PROCEDURE getLineNo () : CARDINAL ;
BEGIN
   IF nextTokNo=0
   THEN
      RETURN 0
   ELSE
      RETURN tokenToLineNo (getTokenNo (), 0)
   END
END getLineNo ;
(*
   getColumnNo - returns the current column where the symbol occurs in
                 the source file.
*)
PROCEDURE getColumnNo () : CARDINAL ;
BEGIN
   IF nextTokNo=0
   THEN
      RETURN 0
   ELSE
      RETURN tokenToColumnNo (getTokenNo (), 0)
   END
END getColumnNo ;
(*
   getTokenNo - returns the current token number.
*)
PROCEDURE getTokenNo () : CARDINAL ;
BEGIN
   IF nextTokNo=0
   THEN
      RETURN 0
   ELSE
      RETURN nextTokNo-1
   END
END getTokenNo ;
(*
   findtokenBucket - returns the tokenBucket corresponding to the tokenNo.
*)
PROCEDURE findtokenBucket (VAR tokenNo: CARDINAL) : tokenBucket ;
VAR
   b: tokenBucket ;
BEGIN
   b := listOfTokens.head ;
   WHILE b#NIL DO
      WITH b^ DO
         IF tokenNo<len
         THEN
            RETURN b
         ELSE
            DEC (tokenNo, len)
         END
      END ;
      b := b^.next
   END ;
   RETURN NIL
END findtokenBucket ;
(*
   tokenToLineNo - returns the line number of the current file for the
                   tokenNo.  The depth refers to the include depth.
                   A depth of 0 is the current file, depth of 1 is the file
                   which included the current file.  Zero is returned if the
                   depth exceeds the file nesting level.
*)
PROCEDURE tokenToLineNo (tokenNo: CARDINAL; depth: CARDINAL) : CARDINAL ;
VAR
   b: tokenBucket ;
   l: sourceList ;
BEGIN
   b := findtokenBucket (tokenNo) ;
   IF b=NIL
   THEN
      RETURN 0
   ELSE
      IF depth=0
      THEN
         RETURN b^.buf[tokenNo].line
      ELSE
         l := b^.buf[tokenNo].file^.left ;
         WHILE depth>0 DO
            l := l^.left ;
            IF l=b^.buf[tokenNo].file^.left
            THEN
               RETURN 0
            END ;
            DEC (depth)
         END ;
         RETURN l^.line
      END
   END
END tokenToLineNo ;
(*
   tokenToColumnNo - returns the column number of the current file for the
                     tokenNo. The depth refers to the include depth.
                     A depth of 0 is the current file, depth of 1 is the file
                     which included the current file. Zero is returned if the
                     depth exceeds the file nesting level.
*)
PROCEDURE tokenToColumnNo (tokenNo: CARDINAL; depth: CARDINAL) : CARDINAL ;
VAR
   b: tokenBucket ;
   l: sourceList ;
BEGIN
   b := findtokenBucket (tokenNo) ;
   IF b=NIL
   THEN
      RETURN 0
   ELSE
      IF depth=0
      THEN
         RETURN b^.buf[tokenNo].col
      ELSE
         l := b^.buf[tokenNo].file^.left ;
         WHILE depth>0 DO
            l := l^.left ;
            IF l=b^.buf[tokenNo].file^.left
            THEN
               RETURN 0
            END ;
            DEC (depth)
         END ;
         RETURN l^.col
      END
   END
END tokenToColumnNo ;
(*
   findFileNameFromToken - returns the complete FileName for the appropriate
                           source file yields the token number, tokenNo.
                           The, Depth, indicates the include level: 0..n
                           Level 0 is the current. NIL is returned if n+1
                           is requested.
*)
PROCEDURE findFileNameFromToken (tokenNo: CARDINAL; depth: CARDINAL) : String ;
VAR
   b: tokenBucket ;
   l: sourceList ;
BEGIN
   b := findtokenBucket (tokenNo) ;
   IF b=NIL
   THEN
      RETURN NIL
   ELSE
      l := b^.buf[tokenNo].file^.left ;
      WHILE depth>0 DO
         l := l^.left ;
         IF l=b^.buf[tokenNo].file^.left
         THEN
            RETURN NIL
         END ;
         DEC (depth)
      END ;
      RETURN l^.name
   END
END findFileNameFromToken ;
(*
   getFileName - returns a String defining the current file.
*)
PROCEDURE getFileName () : String ;
BEGIN
   RETURN findFileNameFromToken (getTokenNo (), 0)
END getFileName ;
PROCEDURE stop ; BEGIN END stop ;
(*
   addTokToList - adds a token to a dynamic list.
*)
PROCEDURE addTokToList (t: toktype; n: Name;
                        i: INTEGER; comment: commentDesc;
			l: CARDINAL; c: CARDINAL; f: sourceList) ;
VAR
   b: tokenBucket ;
BEGIN
   IF listOfTokens.head=NIL
   THEN
      NEW (listOfTokens.head) ;
      IF listOfTokens.head=NIL
      THEN
         (* list error *)
      END ;
      listOfTokens.tail := listOfTokens.head ;
      listOfTokens.tail^.len := 0
   ELSIF listOfTokens.tail^.len=MaxBucketSize
   THEN
      assert (listOfTokens.tail^.next=NIL) ;
      NEW (listOfTokens.tail^.next) ;
      IF listOfTokens.tail^.next=NIL
      THEN
         (* list error *)
      ELSE
         listOfTokens.tail := listOfTokens.tail^.next ;
         listOfTokens.tail^.len := 0
      END ;
      INC (listOfTokens.lastBucketOffset, MaxBucketSize)
   END ;
   WITH listOfTokens.tail^ DO
      next := NIL ;
      assert (len # MaxBucketSize) ;
      WITH buf[len] DO
         token := t ;
         str   := n ;
         int   := i ;
	 com   := comment ;
         line  := l ;
         col   := c ;
         file  := f
      END ;
      INC (len)
   END
END addTokToList ;
(*
   isLastTokenEof - returns TRUE if the last token was an eoftok
*)
PROCEDURE isLastTokenEof () : BOOLEAN ;
VAR
   t: CARDINAL ;
   b: tokenBucket ;
BEGIN
   IF listOfTokens.tail#NIL
   THEN
      IF listOfTokens.tail^.len=0
      THEN
         b := listOfTokens.head ;
         IF b=listOfTokens.tail
         THEN
            RETURN FALSE
         END ;
         WHILE b^.next#listOfTokens.tail DO
            b := b^.next
         END ;
      ELSE
         b := listOfTokens.tail
      END ;
      WITH b^ DO
         assert (len>0) ;     (* len should always be >0 *)
         RETURN buf[len-1].token=eoftok
      END
   END ;
   RETURN FALSE
END isLastTokenEof ;
(* ***********************************************************************
 *
 * These functions allow m2.flex to deliver tokens into the buffer
 *
 ************************************************************************* *)
(*
   addTok - adds a token to the buffer.
*)
PROCEDURE addTok (t: toktype) ;
BEGIN
   IF NOT ((t=eoftok) AND isLastTokenEof())
   THEN
      addTokToList (t, NulName, 0, NIL,
                    mcflex.getLineNo (), mcflex.getColumnNo (), currentSource) ;
      currentUsed := TRUE
   END
END addTok ;
(*
   addTokCharStar - adds a token to the buffer and an additional string, s.
                    A copy of string, s, is made.
*)
PROCEDURE addTokCharStar (t: toktype; s: ADDRESS) ;
BEGIN
   IF strlen(s)>80
   THEN
      stop
   END ;
   addTokToList (t, makekey (s), 0, NIL,
		 mcflex.getLineNo (), mcflex.getColumnNo (), currentSource) ;
   currentUsed := TRUE
END addTokCharStar ;
(*
   addTokInteger - adds a token and an integer to the buffer.
*)
PROCEDURE addTokInteger (t: toktype; i: INTEGER) ;
VAR
   s: String ;
   c,
   l: CARDINAL ;
BEGIN
   l := mcflex.getLineNo () ;
   c := mcflex.getColumnNo () ;
   s := Sprintf1 (Mark (InitString ('%d')), i) ;
   addTokToList (t, makekey(string(s)), i, NIL, l, c, currentSource) ;
   s := KillString (s) ;
   currentUsed := TRUE
END addTokInteger ;
(*
   addTokComment - adds a token to the buffer and a comment descriptor, com.
*)
PROCEDURE addTokComment (t: toktype; com: commentDesc) ;
BEGIN
   addTokToList (t, NulName, 0, com,
		 mcflex.getLineNo (), mcflex.getColumnNo (), currentSource) ;
   currentUsed := TRUE
END addTokComment ;
BEGIN
   init
END mcLexBuf.