簡體   English   中英

將BNF翻譯成Parsec程序有什么訣竅嗎?

[英]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包含組合器optionoptionMaybe ,當您需要“可能做某事”時,它們非常有用。

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.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM