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

2024-03-25

匹配函数调用链的 BNF(如x(y)(z)...):

expr = term T
T    = (expr) T
      | EMPTY
term = (expr)
      | VAR 

将其转换为秒差距程序,看起来很棘手。

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)

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

lexer :: Lexer [Token]
lexer = spaces >> many1 (oneToken <* spaces) 

注意放置的位置spaces:这样,字符串的开头和结尾处都会接受空格。

Since 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 and optionMaybe当您需要“也许做某事”时,这很有用。

expr = do 
  e0 <- term 
  option e0 (parens expr >>= \e1 -> return (App e0 e1))

最后一行的意思是 - 尝试应用给定的解析器option- 如果失败,则返回e0.

为了测试你可以这样做:

tokAndParse = runParser (lexer <* eof) () "" >=> runParser (expr <* eof) () ""

The 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"))
本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

将 BNF 转换为 Parsec 程序有什么技巧吗? 的相关文章

  • Haskell:单词,取消单词分隔符

    有什么办法可以提供分隔符words and unwords在haskell中 使其类似于python中的split和join 另请查看友好的包裹split 它提供了一个模块Data List Split http hackage haske
  • 使用类型类将 Haskell 中的值与类型关联起来

    我想使用类型类返回String功能上依赖于 Haskell 类型的实例 例如 假设我们有这样的类型Form 我想将字符串 form 与此类型相关联 给定类型Invocation 我想关联字符串 job 等等 重要的是我通常不会有相关类型的实
  • 为什么 TypeSynonymInstances 不允许在实例头中使用部分应用的类型同义词?

    我知道TypeSynomymInstances 只允许在实例头中使用完全应用的类型同义词 http hackage haskell org trac haskell prime wiki TypeSynonymInstances 但如果我也
  • 并行解析器存在哪些概念或算法?

    对于已经以分割格式给出的大量输入数据 并行化解析器似乎很容易 例如单个数据库条目的大列表 或者很容易通过快速预处理步骤进行分割 例如解析大型文本中句子的语法结构 并行解析似乎有点困难 它已经需要相当多的努力来定位给定输入中的子结构 通用编程
  • 从 Java / C# 角度理解 C++ 编译器

    我是一名经验丰富的 Java C 程序员 最近开始学习 C 问题是 我无法理解如何构建各种头文件和代码文件 这似乎主要是由于我对编译器如何将所有内容链接在一起缺乏了解 我尝试阅读一些教科书 但我的先入之见受到我的 Java 和 C 知识的影
  • 使用 VB.NET 循环遍历 XML 文件

    我在处理 XMl 文件时遇到问题 我想循环 使用 VB NET 该文件并提取 OrderID 元素的所有值
  • 文本段的名称从何而来?

    传统的汇编器和更高级别的编译器使用多个内存segments 根据预期用途 因此 有数据段 堆栈段 bss 和文本段 文本段也称为代码段 Text部分 为了机器码 我问过所有我能找到的老前辈 像机器代码这样难以阅读的东西是如何被称为 文本段
  • 证明 Applicative 和 Monad 的序列定义的等价性

    我怎样才能正确地证明这一点 sequenceA Traversable t Applicative f gt t f a gt f t a sequenceA pure sequenceA x xs pure lt gt x lt gt s
  • 将单词的第一个字母大写,同时删除空格(Haskell)

    我刚刚开始使用 Haskell 这就像我正在写的第三件事 所以 自然地 我发现自己有点困惑 我正在尝试编写一些代码 该代码将获取一个字符串 删除空格 并将该字符串的每个字母大写 例如 如果我输入 这是一个测试 我想返回类似 thisIsAT
  • 在 Haskell 中的列表末尾添加一个元素

    我是 Haskell 的初学者 我正在尝试在列表末尾添加一个元素 我输入一个像 1 2 3 4 这样的列表和一个数字 10 我想要一个像这样的输出 1 2 3 4 10 My code func a a func a x xs x func
  • 在Python中解析.iso文件[重复]

    这个问题在这里已经有答案了 我想用 python 解析 iso 文件 我想从 iso 获取信息和数据 例如有一个 iso 文件 其名称为 xyz iso 但实际上它是一个 ubuntu 映像 并且包含 Readme txt deb pacg
  • Haskell 将两个列表中不同索引处的元素组合起来

    对这个糟糕的标题表示歉意 我不太确定如何用语言描述它 但这就是我的意思 如果您知道更好的表达方式 请告诉我 假设我有 2 个长度相等的列表 a b c x y z 我想创建列表 a y z b x z c x y 本质上 对于 list1
  • 在 R 中解析和评估字符串表达式的列?

    如何将 R 中的一列字符串表达式作为管道的一部分进行解析和求值 在下面的示例中 我生成了所需的列 evaluated 但我知道这不是正确的做法 我尝试采取 tidyverse 方法 但我只是很困惑 library tidyverse df
  • 由相同数据类型的不同构造函数共享的 Haskell 记录访问器

    关于 Haskell 记录的基本问题 如果我定义这个数据类型 data Pet Dog name String Cat name String deriving Show 以下作品 main do let d Dog name Spot c
  • 解析末尾带有值修饰符('-'、'%')的字符串

    我尝试着去掌握解析 我有一些数据来自de de格式在字符串末尾带有附加信息 我设法使 de de 部分正确 但我很难得到 and 解析正确 我读了codecvt但我不明白这个话题 这是我迄今为止所理解的反映以及我需要做的事情的示例 incl
  • 如何用Java编写某些语法的LALR解析器?

    我想编写 Java 代码来为我的语法构建 LALR 解析器 有人可以推荐一些书籍或一些链接 让我可以学习如何为 LALR 解析器编写 Java 代码吗 手动编写 LALR 解析器很困难 但他可以做到 如果您想了解手动构建解析器背后的理论 请
  • 如何将 XML 数据解析为 PHP 变量

    我对 php 很平庸 对 XML 一无所知 如果你能详细一点 它将帮助我学习 我正在尝试使用 PHP 编程来执行此链接的脚本 VARIABLE ZIP 是在表单中输入的实际邮政编码 该表单将在上面的链接中提交信息 该链接的输出创建了一个 X
  • 通过列表搜索

    我一直在尝试定义一个函数 给定一个整数列表和一个整数 n 返回一个布尔值 指示 n 是否在列表中恰好出现一次 我有这个 但它不起作用 我无法弄清楚 once a gt a gt Bool gt Bool filter filter p x
  • Java 文档生成器为 Xml 字符串返回 null 文档

    I have tried all possible answers https stackoverflow com questions 33098082 passing xml string as document returning nu
  • 功能段落

    抱歉 我还不太明白 FP 我想将一系列行分割成一系列行序列 假设一个空行作为段落划分 我可以在 python 中这样做 如下所示 def get paraghraps lines paragraphs paragraph for line

随机推荐