Next / Previous / Contents / Shipman's homepage

21. The Scan class

The intended functions below are the formal descriptions of the interface. They should match the informal descriptions given in Section 4, “The Scan class: Managing progress through a stream”.

Before the actual class interface definition, we define one verification function that describes the behavior of position arguments to methods such as .tab().

logscan.py
# - - - - -   S p e c i f i c a t i o n  f u n c t i o n s

# effective-pos(p) ==
#   if p is nonnegative ->
#     p
#   else ->
#     (length of the current line) + 1 + p
#


# - - - - -   c l a s s   S c a n

class Scan(object):
    '''Stream scanning class.

      Exports:
        Scan(inFile, commentPrefix=None, callback=None):
          [ (inFile is a string or file-like object) and
            (commentPrefix is a string or None) and
            (callback is a function or None) ->
              if (fileName names a readable file) ->
                return a new Scan object with (commentPrefix)
                as the comment character (if given) and
                using (callback) as the callback procedure
                (if given) and positioned at the beginning
                of the first line (if there is one)
              else if (fileName is a readable file handle) ->
                return a new Scan object with (commentPrefix)
                as the comment character (if given) and
                using (callback) as the callback procedure
                (if given), reading from fileName at its
                current position
              else ->
                  raise IOError ]
        .atEndFile:  # Read-only
          [ if self is positioned at the end of the stream ->
              True
            else -> False ]
        .line:  # Read-only
          [ if self is not at the end of the stream ->
              the current line, with line terminator and
              comment (if any) removed
            else -> (undefined) ]
        .rawLine:  # Read-only
          [ if self is not at the end of the stream ->
              the current line, with line terminator removed
            else -> (undefined) ]
        .lineNo:  # Read-only
          [ the line number of the current line in self,
            counting from 1 ]
        .pos:  # Read-only
          [ if self is not at the end of the stream ->
              the position within the current line, counting
              from 0
            else -> (undefined) ]
        .close():    [ self  :=  self, closed ]
        .atEndLine():
          [ if self is at the end of the stream or the end of
            the current line ->
              return True
            else -> return False ]
        .nextLine():
          [ if self is at the end of the stream ->
              return False
            else ->
              self  :=  self advanced to the beginning of the
                        next line ]
        .error(*L):
          [ L is a list of strings ->
              if any message has been issued for the current line ->
                Log()  +:=  an error message showing the current
                    line and position of self, with the concatenated
                    elements of L used as the message
              else ->
                Log()  +:=  (current raw line) + (an error
                    message showing the current line and position
                    of self, with the concatenated elements of L
                    used as the message ]
        .syntax(*L):
          [ like .error() but raises SyntaxError after
            transmitting the error message ]
        .warning(*L):
          [ like .error() but issues a warning rather than an
            error message ]
        .msgKind(kind, *L):
          [ (kind is a string) and
            (L is a list of strings) ->
              like .error(), but issues a message of kind (kind) ]
        .message(*L):
          [ like .error() but uses Log().message() ]
        .write(*L):
          [ like .error() but uses Log().write() ]
        .move(n):
          [ n is a nonnegative int ->
              if there are at least n characters remaining on the
              current line ->
                self  :=  self with the position advanced by n
              else -> raise IndexError ]
        .tab(p):
          [ if effective-pos(p) is within the current line ->
              self  :=  self with the current position moved to
                        effective-pos(p)
            else -> raise IndexError ]
        .isPos(p):
          [ if effective-pos(p) is the current position in the
            current line ->
              return True
            else -> return False ]
        .find(s):
          [ s is a string ->
              if any part of the remainder of the current line
              matches s ->
                return the position on the current line where the
                first such match begins
              else -> return None ]
        .upToRe(r):
          [ r is a regular expression in string or compiled form ->
              if any part of the remainder of the current line
              matches r ->
                return the position on the current line where the
                first such match begins
              else -> return None ]
        .deblankFile():
          [ self  :=  self advanced past any leading whitespace,
                over any number of lines ]
        .deblankLine():
          [ self  :=  self advanced past any leading whitespace,
                but not past end of line ]
        .match(s):
          [ s is a string ->
              if the current position starts with s ->
                return the position just after the match
              else -> return None ]
        .matchArb(s):
          [ s is a string ->
              if the current position starts with s, case-insensitive ->
                return the position just after the match
              else -> return None ]
        .tabMatch(s):
          [ s is a string ->
              if the current line starts with s ->
                self  :=  self advanced past the match
                return s
              else -> return None ]
        .tabMatchArb(s):
          [ s is a string ->
              if the current line starts with s, case-insensitive ->
                self  :=  self advanced past the match
                return s
              else -> return None ]
        .reMatch(r):
          [ r is a regular expression as a string or compiled ->
              if r matches at the current position on the current
              line ->
                return a MatchObject representing the match
              else -> return None ]
        .tabReMatch(r):
          [ r is a regular expression as a string or compiled ->
              if r matches at the current position on the current
              line ->
                self  :=  self advanced past the matching part
                return a MatchObject representing the match
              else -> return None ]
        .integer(maxLen=None):
          [ maxLen is an int, defaulting to 2**31-1 ->
              if the current line at the current position starts
              with one or more digits, preceded by an optional
              "+" or "-" ->
                self  :=  self advanced past all that
                return all that as an int
              else -> return None ]
        .fixed():
          [ if the current line at the current position starts
            with one or more digits, optionally preceded by
            "+" or "-", and containing at most one "." ->
              self  :=  self advanced past all that
              return all that as a float
            else -> return None ]
        .flatInt(n):
          [ n is a positive integer ->
              if the current line at the current position starts
              with an integer right-justified in a field of size n ->
                self  :=  self advanced by n
                return that integer as type int
              else -> return None ]

The above is the exported interface. Next come the internal state variables.

logscan.py
      State/Invariants:
        .fileName:
          [ if the constructor was called with a string ->
              that string
            else -> None ]
        .file:
          [ if self is closed -> None
            else ->
              readable file handle for the input stream ]
        .commentPrefix:    [ as passed to the constructor ]
        .callback:         [ as passed to the constructor ]
        .__echoed:
          [ if any messages have been issued during the current
            line ->
              True
            else -> False ]
  '''