[英]Is there any trick about translating BNF to Parsec program?
匹配函數調用鏈的BNF(如x(y)(z)...
):
expr = term T
T = (expr) T
| EMPTY
term = (expr)
| VAR
把它翻譯成看起來很棘手的Parsec程序。
term :: Parser Term
term = parens expr <|> var
expr :: Parser Term
expr = do whiteSpace
e <- term
maybeAddSuffix e
where addSuffix e0 = do e1 <- parens expr
maybeAddSuffix $ TermApp e0 e1
maybeAddSuffix e = addSuffix e
<|> return e
你能列出關於將BNF翻譯成Parsec程序的所有設計模式嗎?
如果您的語法很大,最簡單的想法就是使用Alex / Happy組合。 它使用相當簡單,直接接受BNF格式 - 不需要人工翻譯 - 也許最重要的是,產生極快的解析器/詞法分析器。
如果你已經決定使用parsec(或者你正在做這個作為一個學習練習),我發現通常更容易分兩個階段完成它; 先lexing,然后解析。 Parsec會做到這兩點!
首先寫出合適的類型:
{-# LANGUAGE LambdaCase #-}
import Text.Parsec
import Text.Parsec.Combinator
import Text.Parsec.Prim
import Text.Parsec.Pos
import Text.ParserCombinators.Parsec.Char
import Control.Applicative hiding ((<|>))
import Control.Monad
data Term = App Term Term | Var String deriving (Show, Eq)
data Token = LParen | RParen | Str String deriving (Show, Eq)
type Lexer = Parsec [Char] () -- A lexer accepts a stream of Char
type Parser = Parsec [Token] () -- A parser accepts a stream of Token
解析單個令牌很簡單。 為簡單起見,變量是1個或更多個字母。 你當然可以隨意改變它。
oneToken :: Lexer Token
oneToken = (char '(' >> return LParen) <|>
(char ')' >> return RParen) <|>
(Str <$> many1 letter)
解析整個令牌流只是多次解析單個令牌,可能由空格分隔:
lexer :: Lexer [Token]
lexer = spaces >> many1 (oneToken <* spaces)
注意spaces
的位置:這樣,在字符串的開頭和結尾都接受空格。
由於Parser
使用自定義令牌類型,因此您必須使用自定義satisfy
功能。 幸運的是,這幾乎與現有的滿足相同。
satisfy' :: (Token -> Bool) -> Parser Token
satisfy' f = tokenPrim show
(\src _ _ -> incSourceColumn src 1)
(\x -> if f x then Just x else Nothing)
然后我們可以為每個原始令牌編寫解析器。
lparen = satisfy' $ \case { LParen -> True ; _ -> False }
rparen = satisfy' $ \case { RParen -> True ; _ -> False }
strTok = (\(Str s) -> s) <$> (satisfy' $ \case { Str {} -> True ; _ -> False })
你可以想象, parens
對我們的目的是有用的。 寫作非常簡單。
parens :: Parser a -> Parser a
parens = between lparen rparen
現在有趣的部分。
term, expr, var :: Parser Term
term = parens expr <|> var
var = Var <$> strTok
這兩個對你來說應該是相當明顯的。
Parec包含組合器option
和optionMaybe
,當您需要“可能做某事”時,它們非常有用。
expr = do
e0 <- term
option e0 (parens expr >>= \e1 -> return (App e0 e1))
最后一行表示 - 嘗試將給定的解析器應用於option
- 如果失敗,則返回e0
。
對於測試,您可以:
tokAndParse = runParser (lexer <* eof) () "" >=> runParser (expr <* eof) () ""
附加到每個解析器的eof
是為了確保消耗整個輸入; 如果有額外的尾隨字符,則字符串不能是語法的成員。 注意 - 您的示例x(y)(z)
實際上並不在您的語法中!
>tokAndParse "x(y)(z)"
Left (line 1, column 5):
unexpected LParen
expecting end of input
但以下是
>tokAndParse "(x(y))(z)"
Right (App (App (Var "x") (Var "y")) (Var "z"))
聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.