2012-08-28 15 views
6

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"

+1

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

+0

@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

+0

@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

Antwort

5

A Anfang erwartet Lösung kann sein, um Ihre ch zu definieren Stimm-Funktion in der Parser verwenden eine bestimmte unerwartete Funktion unerwarteter Fehler außer Kraft zu setzen und schließlich verwenden die <?> Bediener die erwartete Nachricht außer Kraft zu setzen:

mychoice [] = mzero 
mychoice (x:[]) = (tok x <|> myUnexpected) <?> show x 
mychoice (x:xs) = ((tok x <|> mychoice xs) <|> myUnexpected) <?> show (x:xs) 

myUnexpected = do 
      input <- getInput 
      unexpected $ (id $ first input) 
      where 
      first [] = "eof" 
      first (x:xs) = show $ fst x 

und rufen Sie Ihren Parser wie folgt aus:

ghci> Parser.parseTest (mychoice [Ide "ok", Ide "nop"]) "asdf " 
parse error at (line 1, column 1): 
unexpected Ide "asdf" 
expecting [Ide "ok",Ide "nop"] 
+1

Danke. Ich habe meinen letzten Code zur Frage hinzugefügt. – sinan