Ich arbeite an der Trennung von Lexing und Parsing Phasen eines Parsers. Nach einigen Tests habe ich festgestellt, dass Fehlermeldungen weniger hilfreich sind, wenn ich andere Token als Parsec Char-Tokens verwende.Haskell Parsec - Fehlermeldungen sind weniger hilfreich bei der Verwendung benutzerdefinierter Token
Hier sind einige Beispiele von Fehlermeldungen des Parsec während Char mit Token:
ghci> P.parseTest (string "asdf" >> spaces >> string "ok") "asdf wrong"
parse error at (line 1, column 7):
unexpected "w"
expecting space or "ok"
ghci> P.parseTest (choice [string "ok", string "nop"]) "wrong"
parse error at (line 1, column 1):
unexpected "w"
expecting "ok" or "nop"
So String-Parser zeigt, welche Zeichenfolge erwartet wird, wenn eine unerwartete Zeichenfolge gefunden, und die Wahl Parser zeigt, welche Alternativen gibt.
Aber wenn ich gleichen combinators mit meinem Token verwenden:
ghci> Parser.parseTest ((tok $ Ide "asdf") >> (tok $ Ide "ok")) "asdf "
parse error at "test" (line 1, column 1):
unexpected end of input
In diesem Fall ist es nicht drucken, was erwartet wurde.
ghci> Parser.parseTest (choice [tok $ Ide "ok", tok $ Ide "nop"]) "asdf "
parse error at (line 1, column 1):
unexpected (Ide "asdf","test" (line 1, column 1))
Und wenn ich choice
verwenden, spielt es keine Alternativen drucken.
Ich erwarte, dass dieses Verhalten mit Kombinatorfunktionen und nicht mit Tokens zusammenhängt, aber scheint, dass ich falsch liege. Wie kann ich das beheben?
Hier ist der vollständige Lexer + Parsercode:
Lexer:
module Lexer
(Token(..)
, TokenPos(..)
, tokenize
) where
import Text.ParserCombinators.Parsec hiding (token, tokens)
import Control.Applicative ((<*), (*>), (<$>), (<*>))
data Token = Ide String
| Number String
| Bool String
| LBrack
| RBrack
| LBrace
| RBrace
| Keyword String
deriving (Show, Eq)
type TokenPos = (Token, SourcePos)
ide :: Parser TokenPos
ide = do
pos <- getPosition
fc <- oneOf firstChar
r <- optionMaybe (many $ oneOf rest)
spaces
return $ flip (,) pos $ case r of
Nothing -> Ide [fc]
Just s -> Ide $ [fc] ++ s
where firstChar = ['A'..'Z'] ++ ['a'..'z'] ++ "_"
rest = firstChar ++ ['0'..'9']
parsePos p = (,) <$> p <*> getPosition
lbrack = parsePos $ char '[' >> return LBrack
rbrack = parsePos $ char ']' >> return RBrack
lbrace = parsePos $ char '{' >> return LBrace
rbrace = parsePos $ char '}' >> return RBrace
token = choice
[ ide
, lbrack
, rbrack
, lbrace
, rbrace
]
tokens = spaces *> many (token <* spaces)
tokenize :: SourceName -> String -> Either ParseError [TokenPos]
tokenize = runParser tokens()
Parser:
module Parser where
import Text.Parsec as P
import Control.Monad.Identity
import Lexer
parseTest :: Show a => Parsec [TokenPos]() a -> String -> IO()
parseTest p s =
case tokenize "test" s of
Left e -> putStrLn $ show e
Right ts' -> P.parseTest p ts'
tok :: Token -> ParsecT [TokenPos]() Identity Token
tok t = token show snd test
where test (t', _) = case t == t' of
False -> Nothing
True -> Just t
LÖSUNG:
Ok, nach Antwort des fp4me und Lesen Char Parsec Quelle sorgfältiger, ich endete damit:
{-# LANGUAGE FlexibleContexts #-}
module Parser where
import Text.Parsec as P
import Control.Monad.Identity
import Lexer
parseTest :: Show a => Parsec [TokenPos]() a -> String -> IO()
parseTest p s =
case tokenize "test" s of
Left e -> putStrLn $ show e
Right ts' -> P.parseTest p ts'
type Parser a = Parsec [TokenPos]() a
advance :: SourcePos -> t -> [TokenPos] -> SourcePos
advance _ _ ((_, pos) : _) = pos
advance pos _ [] = pos
satisfy :: (TokenPos -> Bool) -> Parser Token
satisfy f = tokenPrim show
advance
(\c -> if f c then Just (fst c) else Nothing)
tok :: Token -> ParsecT [TokenPos]() Identity Token
tok t = (Parser.satisfy $ (== t) . fst) <?> show t
Jetzt erhalte ich gleiche Fehlermeldungen:
GHCI> Parser.parseTest (Wahl [tok $ Ide "ok", tok $ Ide "nop"]) "asdf"
Parse Fehler bei (Zeile 1, Spalte 1):
unerwarteten (Ide "asdf", "test" (Zeile 1, Spalte 3))
Ide "ok" oder Ide "NOP"
Warum Willst du Lexing vom Parsing trennen? Der Hauptgrund dafür ist sicherlich die Tradition - es war einfacher, einen trickreichen Parser zu schreiben, der frei von den Implementierungsdetails des Lexers war (was eher routinemäßig war, vielleicht nur reguläre Ausdrücke), und in Imperativsprachen macht es das Denken einfacher, die zu trennen Phasen.Im schönen Haskell Parsec Land ist das Schreiben der Lexer und der Parser einfach und leicht: lex einige Strings, kombiniere sie, um sie zu parsen - du kannst fast die Definition deiner Sprache in Kombinatoren schreiben. Außerdem arbeiten Sie hart, um Positionen durchzugehen; lass Parsec es tun. – AndrewC
@AndrewC, Sie können Recht haben. Ich wollte nur die guten und schlechten Teile der Trennung von Lexing- und Parsing-Phasen in Parsec sehen. Jetzt, nachdem ich meinen letzten Code gelesen habe, denke ich, dass ich nur Parser verwenden werde. (Auch wenn ich alex + benutzte, um eine indentation-basierte Grammatik zu parsen und zu lexen, half mir, Indent-Token zu erzeugen, und ließ den Parser an vereinfachter Grammatik arbeiten. Eine separate Lexing-Phase in Parsec könnte auch in dieser Art von Situationen helfen) – sinan
@AndrewC, auch, ich liebe Parsec wirklich und ich denke, in der Lage zu sein auf verschiedene Arten von Streams (außer Charakter Streams) arbeiten kann wirklich hilfreich sein und das Schreiben eines Lexers half mir zu verstehen, wie kann ich das tun. Jetzt weiß ich, wie ich zum Beispiel an Byte-Strings arbeiten kann. – sinan