【问题标题】:Haskell Parsec - error messages are less helpful while using custom tokensHaskell Parsec - 使用自定义令牌时错误消息的帮助不大
【发布时间】:2012-08-28 20:50:08
【问题描述】:

我正在致力于将解析器的词法分析和解析阶段分开。经过一些测试,我意识到当我使用 Parsec 的 Char 令牌以外的其他令牌时,错误消息的帮助不大。

以下是 Parsec 在使用 Char 令牌时的错误消息示例:

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 时,它不会打印替代品。

我希望这种行为与组合函数有关,而不是与标记有关,但似乎我错了。我该如何解决这个问题?

这是完整的词法分析器 + 解析器代码:

词法分析器:

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

解决方案:

好的,在 fp4me 的回答和更仔细地阅读 Parsec 的 Char 源之后,我得到了这个:

{-# 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 (choice [tok $ Ide "ok", tok $ Ide "nop"]) "asdf"
解析错误(第 1 行,第 1 列):
意外(Ide "asdf","test"(第 1 行,第 3 列))
期待 Ide "ok" 或 Ide "nop"

【问题讨论】:

  • 为什么要将词法分析与解析分开?这样做的主要原因当然是传统 - 编写一个没有词法分析器实现细节的棘手解析器更简单(这更常规,可能只是正则表达式),并且在命令式语言中,它使思考更容易分离阶段。在 Haskell Parsec 领域,编写词法分析器和解析器非常简单:对一些字符串进行词法分析,将它们组合起来进行解析——您几乎可以在组合器中编写语言的定义。此外,您正在努力通过职位;让 Parsec 来做吧。
  • @AndrewC,你可能是对的。我只是想看看在 parsec 中分离词法分析和解析阶段的好坏部分。现在,在我查看我的最终代码之后,我想我将只使用解析器。 (此外,一旦我使用 alex+happy 解析基于缩进的语法,词法分析帮助我生成缩进+缩进标记,并让解析器处理简化的语法。在这种情况下,parsec 中的单独词法分析阶段也有帮助)
  • @AndrewC,另外,我真的很喜欢 Parsec,我认为能够处理不同类型的流(除了字符流)真的很有帮助,并且编写一个词法分析器帮助我理解我该如何去做。例如,现在我知道如何处理字节字符串了。

标签: parsing haskell lexical-analysis parsec


【解决方案1】:

解决方案的开始可以是在 Parser 中定义您的选择函数, 使用特定的意外函数来覆盖意外错误,最后 使用 &lt;?&gt; 运算符覆盖预期消息:

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

然后像这样调用你的解析器:

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

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 2016-06-21
    • 2010-10-29
    • 2021-05-30
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2016-01-01
    • 2017-07-03
    相关资源
    最近更新 更多