我正在分離分析器的lexing和分析階段。經過一些測試後,我意識到錯誤消息在我使用除Parsec的Char標記以外的某些標記時幫助不大。Haskell Parsec - 錯誤消息在使用自定義令牌時不太有用
這裏是秒差距的錯誤消息的一些例子,而使用字符標記:
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"
所以,串分析器顯示了發現一個意外的字符串時,和選擇解析器說明了什麼是替代字符串的預期。
但是,當我用我的令牌相同的組合子:
ghci> Parser.parseTest ((tok $ Ide "asdf") >> (tok $ Ide "ok")) "asdf "
parse error at "test" (line 1, column 1):
unexpected end of input
在這種情況下,不打印什麼預期。
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))
而當我使用choice
時,它不打印替代品。
我希望這種行爲與combinator函數有關,而不是與令牌,但似乎我錯了。我怎樣才能解決這個問題?
下面是完整的詞法分析器+解析器代碼:
詞法:
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()
分析器:
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
SOLUTION:
好吧,以後fp4me的答案和閱讀秒差距的字符源更仔細,我結束了這個:
{-# 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
現在我得到同樣的錯誤信息:
ghci中> Parser.parseTest(選擇[TOK $ IDE 「OK」,TOK $ IDE 「NOP」]) 「ASDF」
解析誤差在(第1行,第1列):
意外(IDE 「ASDF」, 「測試」(第1行,第3列))
期待Ide的 「OK」 或IDE 「NOP」
爲什麼你想分離lexing從解析?這樣做的主要原因是傳統 - 編寫一個棘手的解析器比詞法分析器的實現細節更簡單(這是更常規的,也許只是正則表達式),並且在命令式語言中,它使得思考更容易分離階段。在好的Haskell Parsec土地上,編寫詞法分析器和解析器非常簡單:使用一些字符串,將它們組合起來解析它們 - 您幾乎可以在組合器中編寫語言的定義。此外,你正在努力通過職位,讓Parsec去做吧。 – AndrewC
@AndrewC,你可能是對的。我只是想看看parsec中分離lexing和解析階段的好處和壞處。現在看了我的最終代碼之後,我想我會只用解析器。 (另外,一旦我使用alex + happy來解析基於縮進的語法,lexing幫助我生成縮進+縮進符號,並讓解析器工作在簡化語法上。parsec中單獨的lexing階段也可以幫助解決這種情況) – sinan
@AndrewC,我也非常喜歡Parsec,我認爲能夠處理不同類型的流(字符流除外)可以非常有幫助,編寫一個詞法分析器幫助我理解我該怎麼做。例如,現在我知道如何處理字節字符串。 – sinan