gpt4 book ai didi

parsing - 将 BNF 转换为 Parsec 程序有什么技巧吗?

转载 作者:行者123 更新时间:2023-12-02 18:27:48 25 4
gpt4 key购买 nike

匹配函数调用链的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 格式 - 不需要人工翻译 - 也许最重要的是,它可以生成速度极快的解析器/词法分析器。

如果您执意要使用秒差距(或者您将其作为学习练习),我发现一般来说分两个阶段进行会更容易;首先词法分析,然后解析。秒差距将两者兼而有之!

首先编写适当的类型:

{-# 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)

解析整个 token 流只是多次解析单个 token ,可能用空格分隔:

lexer :: Lexer [Token]
lexer = spaces >> many1 (oneToken <* 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"))

关于parsing - 将 BNF 转换为 Parsec 程序有什么技巧吗?,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/28828586/

25 4 0
Copyright 2021 - 2024 cfsdn All Rights Reserved 蜀ICP备2022000587号
广告合作:1813099741@qq.com 6ren.com