String -> [ (a, String) ]benutzt. Daß wir Zeichen gelesen hatten, war nicht wesentlich; es hätte irgendein Listentyp sein können. Also verallgemeinern wir zu
[s] -> [ (a, [s]) ]Wir nutzen das wie folgt: wir wollen an jedes Eingabezeichen noch weiter Informationen anheften: seine Position im Quelltext (Zeile und Spalte). Wir definieren einen passenden Typ
module Position where
data Position = Position
{ zeile :: Int
, spalte :: Int
, inhalt :: String -- der gesamten Zeile
}
| EOF
instance Eq Position where
EOF == EOF = True
p @ Position {} == q @ Position {} =
(zeile p, spalte p) == (zeile q, spalte q)
p == q = False
instance Ord Position where
EOF `compare` EOF = EQ
EOF `compare` q = GT
p `compare` EOF = LT
p `compare` q = (zeile p, spalte p) `compare` (zeile q, spalte q)
instance Show Position where
showsPrec _ EOF = showString "Ende des Eingabestroms"
showsPrec _ p @ Position { zeile = z, spalte = s, inhalt = i} =
let (vor, nach) = splitAt (s-1) i
in showString $
unlines [ "Zeile " ++ show z ++ ", Spalte " ++ show s
, vor
, take (s-1) (repeat '^') ++ nach
]
annotate :: String -> [ (Char, Position) ]
annotate input = concat
$ map annotate_line
$ zip [1..]
$ map ( \ l -> l ++ "\n" )
$ lines input
annotate_line :: (Int, String) -> [ (Char, Position) ]
annotate_line (z, i) = do
(s, c) <- zip [1..] i
return $ ( c, Position { zeile = z, spalte = s, inhalt = i } )
und denken dann an eine Benutzung
[(Char, Pos)] -> [ (a, [(Char, Pos)]) ]
[s] -> [ (a, [s]) ]Statt Listen könnten wir auch eine andere Instanz von MonadPlus nehmen. Das führt zum verallgemeinerter Parser-Typ
module General_Parser_Type
( GP (..), run
, MonadPlus (plus)
)
where
import MonadPlus
data GP m s a = GP ( [s] -> m (a, [s]) )
instance Monad m => Functor (GP m s) where
fmap f (GP p) = GP $ \ input ->
do (x, rest) <- p input
return (f x, rest)
instance Monad m => Monad (GP m s) where
return x = GP $ \ input -> return (x, input)
GP p >>= f = GP $ \ input -> do
(x, rest) <- p input
let GP q = f x
(y, rest') <- q rest
return (y, rest')
instance MonadPlus m => MonadPlus (GP m s) where
GP p `plus` GP q = GP $ \ input ->
p input `plus` q input
run :: GP m s a -> [s] -> m (a, [s])
run (GP p) input = p input
Dabei denken wir vorwiegend an Maybe. Das entspricht einer Liste
mit null oder einem Element, beschränkt also die Suchbäume
auf die Breite 1, und beschleunigt damit die Parser.
Es ist jedoch Vorsicht geboten:
Just x `plus` Just y = Just xd. h. wir vergessen das zweite Resultat. Das `plus` auf Maybe ist also nicht symmetrisch; wir müssen uns gut überlegen, in welcher Reihefolge wir die Argumente hinschreiben. Typischer Fall:
many1 :: Parser a -> Parser [a]
many1 p = do x <- p
xs <- many p
return $ x : xs
many :: Parser a -> Parser [a]
many p = many1 p
`plus` return []
Das ist in unserem Sinne richtigrum,
denn das many1 p wird bevorzugt, bis es nicht mehr geht,
und erst dann kommt return [] dran.
module Fehler
( Fehler, falsch, export
)
where
import MonadZero
import MonadPlus
import Position
data Fehler a = Falsch Position
| Richtig a
deriving Show
falsch :: Position -> Fehler a
falsch p = Falsch p
instance Functor Fehler where
fmap f (Falsch p) = Falsch p
fmap f (Richtig x) = Richtig (f x)
instance Monad Fehler where
return x = Richtig x
Richtig x >>= f = f x
Falsch p >>= f = Falsch p
instance MonadPlus Fehler where
Richtig x `plus` _ = Richtig x
Falsch p `plus` Richtig y = Richtig y
Falsch p `plus` Falsch q = Falsch (max p q)
export :: Fehler a -> Either String a
export (Falsch p) = Left (show p)
export (Richtig x) = Right x
Beachte die Instanz von MonadPlus: von zwei fehlgeschlagenen Parses
nehmen wir den längeren, d. h. wir informieren den User
über den längsten korrekten Präfix der Eingabe.
Damit wird unser Parser-Typ zu
module Position_Parser_Basic
( Parser, parse
, item, eof, pos
, guard, plus
)
where
import General_Parser_Type
import Fehler
import Position
import MonadZero
import MonadPlus (plus)
type Parser = GP Fehler (Char, Position)
instance MonadZero Parser where
-- das geht nur mit dem switch hugs -98
-- weil Parser ein Typ-Synonym ist
zero = GP $ \ input -> case input of
((c, p) : cps) -> falsch p
[] -> falsch EOF
item :: Parser Char
item = GP $ \ input -> case input of
((c, p) : cps) -> return (c, cps)
[] -> falsch EOF
eof :: Parser ()
eof = GP $ \ input -> case input of
[] -> return ((), [])
((c, p) : cps) -> falsch p
pos :: Parser Position
pos = GP $ \ input -> case input of
((c, p) : cps) -> return (p, cps)
[] -> return (EOF, [])
parse :: Parser a -> String -> Either String a
parse p input = export $ do
(x, rest) <- run p $ annotate input
return x
Dieses Modul hat das gleiche Inteface wie das ursprüngliche Parser_Basic.hs.
Damit können wir die Parser-Kombinatoren ohne weiteres nachnutzen
(Quelltext)
und auch die Anwendungen
(Quelltext)
merken davon nichts - aber die Fehlermeldungen sind nun viel informativer.