content: %-------------------------------=  --------------------------------------------
  \subsection{Pseudo-\TeX\ Parser}
  %-------------------------------=  --------------------------------------------
  
  %if codeOnly || showModuleHeader
  
  > module TeXParser              (  texparse, newlines  )
  > where
  > import Data.Char              (  isSpace, isAlpha  )
  > import TeXCommands
  > import Data.List              (  isPrefixOf  )
  > import Auxiliaries hiding     (  breaks  )
  
  %endif
  
  Care is taken that no character of the input is lost; this is necessary
  for reporting the correct line number if an error occurs.
  
  > texparse                      :: LineNo -> String -> [Numbered Class]
  > texparse n                    =  number n . compress . classify0 ""
  
  To be able to catch errors the maximum length of arguments is
  restricted to |maxChar| for commands and to |maxLine| for
  environments.
  
  > maxChar, maxLine              :: Int
  > maxChar                       =  1000
  > maxLine                       =  80 * 500
  
  A simple Pseudo-\TeX-Parser. \NB Pseudo-\TeX\ environments must not be
  nested:
  \[
       @\begin{code}...\begin{code}...\end{code}...\end{code}@
  \]
  is not parsed properly.
  
  |classify0| is only used at the start of a file or line; it recognizes
  bird (and inverse bird) tracks.
  
  > classify0                     :: String -> String -> [Class]
  > classify0 _ []                =  []
  > classify0 n ('>' : s)         =  Environment Code (n ++ ' ' : t) : classify0 "" u
  >     where (t, u)              =  unbird '>' s
  > classify0 n ('<' : s)         =  Environment Spec (n ++ ' ' : t) : classify0 "" u
  >     where (t, u)              =  unbird '<' s
  > classify0 n s                 =  Many n : classify s
  
  \NB The preceding newline (if any) is put into the code section to be
  able to suppress blank lines in the \LaTeX\ text.
  
  > classify                      :: String -> [Class]
  > classify []                   =  []
  > classify ('\n' : s)           =  classify0 "\n" s
  
  Commands disguised as comments (AKA pseudo-comments).
  ks, 19.08.2004: changed |classify v| to |classify0 v| calls, to recognize
  (incorrect-Haskell) bird tracks directly after a directive.
  
  > classify ('%' : s)            =  case encode t of
  >         Nothing               -> Many ('%' : t ++ arg)  :  classify0 "" v
  >         Just cmd              -> Directive cmd arg      :  classify0 "" v
  >     where (t, u)              =  break isSpace s
  >           (arg, v)            =  breakAfter (== '\n') u
  
  \NB Text starting with @%@ is ignored; in most cases this is what
  you want (exception @\%@).
  
  Environments.
  
  > classify str@('\\' : s)       =  case span isIdChar s of
  >     ("begin", '{' : t)        -> case span isIdChar t of
  >         (env, '}' : u)        -> case encode env of
  >             Nothing           -> cont
  >             Just cmd
  >                 | p v         -> Environment cmd (arg ++ w) : classify x
  >                 | otherwise   -> notFound end str :  cont
  >                 where
  >                 end           =  "\\end{" ++ env ++ "}"
  >                 p             =  isPrefixOf end
  >                 (arg, v)      =  breaks maxLine p u
  >                 (w, x)        =  blank (drop (length end) v)
  >         _                     -> cont
  
  Inline verbatim commands are treated specially; otherwise @\verb|a|@
  would be mistaken as inline code. Furthermore: then we are able to
  write @\verb|\begin{code}|@.
  
  >     ("verb*", c : t)          -> verbatim True c t
  >     ("verb",  c : t)          -> verbatim False  c t
  
  Commands.
  
  >     (cmd, '{' : t)            -> case encode cmd of
  >         Nothing               -> cont
  >         Just cmd'             -> case nested maxChar 0 t of
  >             (a, '}' : u)      -> Command cmd' a : classify u
  >             _                 -> notFound "matching `}'" str : cont
  >     ([], '%' : t)             -> Many "\\%" : classify t
  >     _                         -> cont
  >     where
  >     cont                      =  One '\\' : classify s
  >     verbatim b c t            =  case verb maxChar c t of
  >         (u, c' : v) | c == c' -> Command (Vrb b) u : classify v
  >         _                     -> notFound ("matching `" ++ [c] ++ "'") str : cont
  
  Inline code.
  
  > classify ('|' : '|' : s)      =  One '|' : classify s
  > classify str@('|' : s)        =  case inline maxChar s of
  >     (arg, '|' : t)            -> Inline arg : classify t
  >     _                         -> notFound "matching `|'" str : One '|' : classify s
  
  Short verb.
  
  > classify ('@' : '@' : s)      =  One '@' : classify s
  > classify str@('@' : s)        =  case shortverb maxChar s of
  >     (arg, '@' : t)            -> Command (Vrb False) arg : classify t
  >     _                         -> notFound "matching `@'" str : One '@' : classify s
  
  Everything else.
  
  > classify (c : s)              =  One c : classify s
  
  > notFound                      :: String -> String -> Class
  > notFound what s               =  Error (what ++ " not found", s)
  
  > isIdChar                      :: Char -> Bool
  > isIdChar c                    =  isAlpha c || c == '*'
  
  % - - - - - - - - - - - - - - - = - - - - - - - - - - - - - - - - - - - - - - -
  \subsubsection{Parsing of arguments}
  % - - - - - - - - - - - - - - - = - - - - - - - - - - - - - - - - - - - - - - -
  
  The parser satisfy
  %
  \begin{eqnarray*}
      |parse M.s = (M.l, M.r)|      & |==>| & |M.s = M.l ++ M.r|
  \end{eqnarray*}
  %
  The function |nested n 0| recognizes arguments enclosed in matching
  curly braces.
  
  > nested                        :: Int -> Int -> String -> (String, String)
  > nested n' depth s'            =  nest n' s'
  >     where
  >     nest 0  s                 =  ([], s)
  >     nest _n []                =  ([], [])
  >     nest n  ('}' : s)
  >         | depth == 0          =  ([], '}' : s)
  >         | otherwise           =  '}' <| nested (n - 1) (depth - 1) s
  >     nest n  ('{' : s)         =  '{' <| nested (n - 1) (depth + 1) s
  >     nest n  ('\\' : c : s)    =  '\\' <| c <| nest (n - 2) s
  >     nest n  (c : s)           =  c <| nest (n - 1) s
  
  The function |verb n c| recognizes arguments enclosed in |c|.
  
  > verb                          :: Int -> Char -> String -> (String, String)
  > verb 0  _c s                  =  ([], s)
  > verb _n _c []                 =  ([], [])
  > verb n c (c' : s)
  >     | c == c'                 =  ([], c' : s)
  >     | otherwise               =  c' <| verb (n - 1) c s
  
  The function |inline n| recognizes arguments enclosed in vertical bars
  (and converts double bars into single bars; therefore it is \emph{not}
  equivalent to |verb n '||'|).
  
  > inline                        :: Int -> String -> (String, String)
  > inline 0  s                   =  ([], s)
  > inline _n []                  =  ([], [])
  > inline n  ('|' : '|' : s)     =  '|' <| inline (n - 2) s
  > inline _n  ('|' : s)          =  ([], '|' : s)
  > inline n  (c : s)             =  c <| inline (n - 1) s
  >
  > shortverb                     :: Int -> String -> (String, String)
  > shortverb 0  s                =  ([], s)
  > shortverb _n []               =  ([], [])
  > shortverb n  ('@' : '@' : s)  =  '@' <| shortverb (n - 2) s
  > shortverb _n ('@' : s)        =  ([], '@' : s)
  > shortverb n  (c : s)          =  c <| shortverb (n - 1) s
  
  The function |unbird| recognizes code sections marked by bird tracks;
  |blank| skips the next line if it is blank.
  
  > unbird                        :: Char -> String -> (String, String)
  > unbird _c []                  =  ([], [])
  > unbird c  ('\n' : c' : s)
  >     | c == c'                 =  '\n' <| ' ' <| unbird c s
  > unbird _c ('\n' : s)          =  '\n' <| blank s
  > unbird c  (c' : s)            =  c' <| unbird c s
  >
  > blank                         :: String -> (String, String)
  > blank s | all isSpace t       =  (t, u)
  >         | otherwise           =  ("", s)
  >         where (t, u)          =  breakAfter (== '\n') s
  
  |breaks n pred as| returns |(x, y)| such that |as = x ++ y|, |pred y|
  holds and |x| is as small as possible (but at most of length |n|).
  
  > breaks                        :: Int -> ([a] -> Bool) -> [a] -> ([a], [a])
  > breaks _n _p []               =  ([], [])
  > breaks n  p  as@(a : as')
  >     | n == 0 || p as          =  ([], as)
  >     | otherwise               =  a <| breaks (n - 1) p as'
  
  % - - - - - - - - - - - - - - - = - - - - - - - - - - - - - - - - - - - - - - -
  \subsubsection{Post processing}
  % - - - - - - - - - - - - - - - = - - - - - - - - - - - - - - - - - - - - - - -
  
  Collaps adjacent |One|'s into a |Many|.
  
  > compress                      :: [Class] -> [Class]
  > compress                      =  foldr (<<|) []
  >     where
  >     One '\n' <<| ts           =  Many "\n" : ts
  >     Many s@('\n' : _) <<| ts  =  Many s : ts
  >     One c <<| (Many s : ts)   =  Many (c : s) : ts
  >     One c <<| ts              =  Many [c] : ts
  >     Many s <<| (Many s' : ts) =  Many (s ++ s') : ts
  >     t <<| ts                  =  t : ts
  
  \NB The first two equations make |compress| incremental; otherwise
  \[
      |do s <- readFile "Examples/InfI.lhs"; mapM_ print (compress (map One s))|
  \]
  is silent until the complete input has been digested.
  
  Adding line numbers.
  
  > number                        :: LineNo -> [Class] -> [Numbered Class]
  > number _n []                  =  []
  > number n  (t : ts)            =  No n t : number (n + i) ts
  >     where i                   =  case t of
  >             One _c            -> impossible "number"
  >             Many s            -> newlines s
  >             Inline s          -> newlines s
  >             Command _ s       -> newlines s
  >             Environment _ s   -> newlines s
  >             Directive _ s     -> newlines s
  >             Error _           -> 0
  
  Number of newline characters in a string.
  
  > newlines                      :: String -> Int
  > newlines s                    =  length [ c | c <- s, c == '\n' ]
  