Warning: file_get_contents(/data/phpspider/zhask/data//catemap/6/haskell/9.json): failed to open stream: No such file or directory in /data/phpspider/zhask/libs/function.php on line 167

Warning: Invalid argument supplied for foreach() in /data/phpspider/zhask/libs/tag.function.php on line 1116

Notice: Undefined index: in /data/phpspider/zhask/libs/function.php on line 180

Warning: array_chunk() expects parameter 1 to be array, null given in /data/phpspider/zhask/libs/function.php on line 181
Parsing Haskell Parsec-使用自定义标记时,错误消息的帮助不大_Parsing_Haskell_Lexical Analysis_Parsec - Fatal编程技术网

Parsing Haskell Parsec-使用自定义标记时,错误消息的帮助不大

Parsing Haskell Parsec-使用自定义标记时,错误消息的帮助不大,parsing,haskell,lexical-analysis,parsec,Parsing,Haskell,Lexical Analysis,Parsec,我正在研究将语法分析器的词法分析和语法分析阶段分开。在一些测试之后,我意识到当我使用除Parsec的Char令牌之外的一些令牌时,错误消息没有那么有用 以下是使用字符标记时Parsec错误消息的一些示例: ghci> P.parseTest (string "asdf" >> spaces >> string "ok") "asdf wrong" parse error at (line 1, column 7): unexpected "w" expecting

我正在研究将语法分析器的词法分析和语法分析阶段分开。在一些测试之后,我意识到当我使用除Parsec的Char令牌之外的一些令牌时,错误消息没有那么有用

以下是使用字符标记时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
时,它不会打印备选方案

我希望这种行为与组合函数有关,而不是与标记有关,但似乎我错了。我怎样才能解决这个问题

以下是完整的lexer+解析器代码:

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 ()
解决方案:

好的,在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
{-#语言灵活上下文}
模块解析器在哪里
将Text.Parsec作为P导入
导入控制.Monad.Identity
进口Lexer
parseTest::Show a=>Parsec[TokenPos]()a->String->IO()
语法测试=
案例标记化的“测试”
左e->putStrLn$show e
右ts'->P.parseTest P ts'
类型Parser a=Parsec[TokenPos]()a
高级::SourcePos->t->[TokenPos]->SourcePos
预付款(pos):=pos
高级职位[]=职位
满足:(TokenPos->Bool)->解析器令牌
满足f=tokenPrim show
进展
(\c->如果是fc,则仅(fst c)其他无)
tok::Token->ParsecT[TokenPos]()标识令牌
tok t=(Parser.success$(==t).fst)show t
现在我收到了相同的错误消息:

ghci>Parser.parseTest(选项[tok$Ide“ok”,tok$Ide“nop”)“asdf”
(第1行第1列)处的分析错误:
意外(Ide“asdf”、“测试”(第1行第3列))
应为Ide“ok”或Ide“nop”


解决方案的开始可以是在解析器中定义您的选择函数, 使用特定的意外函数覆盖意外错误并最终 使用
运算符覆盖预期消息:

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

为什么要将词法分析和语法分析分开?当然,这样做的主要原因是传统——编写一个没有lexer实现细节的复杂解析器更简单(lexer更常规,可能只是正则表达式),而在命令式语言中,它使思想更容易分离阶段。在nice Haskell Parsec land中,编写lexer和解析器非常简单:lex一些字符串,将它们组合起来进行解析——您几乎可以用combinators编写语言的定义。此外,你正在努力通过职位;让帕塞克来做吧。@AndrewC,你可能是对的。我只是想看看在parsec中分离词法分析和解析阶段的优缺点。现在,在我看了最后的代码之后,我想我将只使用解析器。(还有,有一次我使用alex+happy解析基于缩进的语法,词法分析帮助我生成缩进+dedent标记,让解析器处理简化的语法。在这种情况下,parsec中单独的词法分析阶段也会有所帮助)@AndrewC,另外,我真的很喜欢parsec,我认为能够处理不同类型的流(除了字符流)真的很有帮助,编写lexer帮助我理解了如何做到这一点。例如,现在我知道了如何处理字节字符串。
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"]