Als Übung implementiere ich einen Parser für eine äußerst einfache Sprache, die in Haskell mit der folgenden GADT definiert wurde (die echte Grammatik für mein Projekt beinhaltet viel mehr Ausdrücke, aber dieser Auszug ist ausreichend für die Frage):Entfernen der linken Rekursion in einem einfachen Ausdrucksparser
data Expr a where
I :: Int -> Expr Int
Add :: [Expr Int] -> Expr Int
die Parsing-Funktionen sind wie folgt:
expr :: Parser (Expr Int)
expr = foldl1 mplus
[ lit
, add
]
lit :: Parser (Expr Int)
lit = I . read <$> some digit
add :: Parser (Expr Int)
add = do
i0 <- expr
is (== '+')
i1 <- expr
is <- many (is (== '+') *> expr)
pure (Add (i0:i1:is))
Aufgrund der linksrekursive Art des Ausdrucks Grammatik, wenn ich versuche, etwas so einfaches wie 1+1
zu analysieren die Verwendung von expr
Parser, der Parser bleibt in einer Endlosschleife stecken.
Ich habe Beispiele gesehen, wie links Rekursion über die Bahn ausklammern einer Transformation von mit so etwas wie:
S -> S a | b
in so etwas wie:
S -> b T
T -> a T
Aber ich bin zu kämpfen mit wie man das auf meinen Parser anwendet.
Für Vollständigkeit, hier ist der Code, der tatsächlich den Parser implementiert:
newtype Parser a = Parser
{ runParser :: String -> [(a, String)]
}
instance Functor Parser where
fmap f (Parser p) = Parser $ \s ->
fmap (\(a, r) -> (f a, r)) (p s)
instance Applicative Parser where
pure a = Parser $ \s -> [(a, s)]
(<*>) (Parser f) (Parser p) = Parser $ \s ->
concat $ fmap (\(f', r) -> fmap (\(a, r') -> (f' a, r')) (p r)) (f >
instance Alternative Parser where
empty = Parser $ \s -> []
(<|>) (Parser a) (Parser b) = Parser $ \s ->
case a s of
(r:rs) -> (r:rs)
[] -> case b s of
(r:rs) -> (r:rs)
[] -> []
instance Monad Parser where
return = pure
(>>=) (Parser a) f = Parser $ \s ->
concat $ fmap (\(r, rs) -> runParser (f r) rs) (a s)
instance MonadPlus Parser where
mzero = empty
mplus (Parser a) (Parser b) = Parser $ \s -> a s ++ b s
char = Parser $ \case (c:cs) -> [(c, cs)]; [] -> []
is p = char >>= \c -> if p c then pure c else empty
digit = is isDigit
Sie können bei https://en.m.wikipedia.org/wiki/Operator-precedence_parser – dfeuer
auch aussehen wollen, könnte man erwägen 'attoparsec' anstelle Ihrer eigenen Parsing Rahmen rollen. – dfeuer
@dfeuer, aber dann würden wir den Zweck der Übung verpassen! Dieser Operator-Vorrang sieht jedoch wie eine gute Lösung aus. Im Idealfall können wir ihn mit diesem rekursiven Descent-Parser arbeiten lassen. –