使用megaparsec正确解析嵌套数据

如何解决使用megaparsec正确解析嵌套数据

我试图使自己更熟悉megaparsec,并且遇到了一些存在问题。 标题中的“嵌套数据”是指我试图解析类型的事实,而该类型又可能包含其他类型。如果有人可以解释为什么这不符合我的预期,请随时告诉我。

我正在尝试解析类似于在Haskell中找到的类型。类型是基本类型IntBoolFloat或类型变量a(任何小写字母)。 我们还可以从类型构造函数(大写单词)(例如Maybe和类型参数(任何其他类型)构造代数数据类型。示例为Maybe aEither (Maybe Int) Bool。函数与右侧相关联,并由->构成,例如Maybe a -> Either Int (b -> c)。 N元元组是由,分隔并包含在()中的一系列类型,例如(Int,Bool,a)。可以将类型包装在括号中以提高其优先级(Maybe a)。还定义了单位类型()

我正在使用此ADT对此进行描述。

newtype Ident  = Ident String
newtype UIdent = UIdent String
data Type a
    = TLam a (Type a) (Type a)
    | TVar a Ident
    | TNil a
    | TAdt a UIdent [Type a]
    | TTup a [Type a]
    | TBool a
    | TInt a
    | TFloat a

我试图编写一个megaparsec解析器来解析此类类型,但是得到了意外的结果。我在下面附加了相关代码,之后我将尝试描述我的经验。

{-# LANGUAGE OverloadedStrings #-}
module Parser where

import AbsTinyCamiot

import Text.Megaparsec
import Text.Megaparsec.Char
import qualified Text.Megaparsec.Char.Lexer as Lexer
import Text.Megaparsec.Debug

import Control.Applicative hiding (many,some,Const)
import Control.Monad.Combinators.Expr
import Control.Monad.Identity
import Data.Void
import Data.Text (Text,unpack)

type Parser a = ParsecT Void Text Identity a

-- parse types

pBaseType :: Parser (Type ())
pBaseType = choice [
    TInt   () <$  label "parse int"           (pSymbol "Int"),TBool  () <$  label "parse bool"          (pSymbol "Bool"),TFloat () <$  label "parse float"         (pSymbol "Float"),TNil   () <$  label "parse void"          (pSymbol "()"),TVar   () <$> label "parse type variable" pIdent]

pAdt :: Parser (Type ())
pAdt = label "parse ADT" $ do
    con <- pUIdent
    variables <- many $ try $ many spaceChar >> pType
    return $ TAdt () con variables

pType :: Parser (Type ())
pType = label "parse a type" $ 
        makeExprParser 
        (choice [ try pFunctionType,try $ parens pType,try pTupleType,try pBaseType,try pAdt
                ]) 
        []--[[InfixR (TLam () <$ pSymbol "->")]]

pTupleType :: Parser (Type ())
pTupleType = label "parse a tuple type" $ do
    pSymbol "("
    fst  <- pType
    rest <- some (pSymbol "," >> pType)
    pSymbol ")"
    return $ TTup () (fst : rest)

pFunctionType :: Parser (Type ())
pFunctionType = label "parse a function type" $ do
    domain <- pType
    some spaceChar
    pSymbol "->"
    some spaceChar
    codomain <- pType
    return $ TLam () domain codomain

parens :: Parser a -> Parser a
parens p = label "parse a type wrapped in parentheses" $ do
    pSymbol "("
    a <- p
    pSymbol ")"
    return a

pUIdent :: Parser UIdent
pUIdent = label "parse a UIdent" $ do
    a <- upperChar
    rest <- many $ choice [letterChar,digitChar,char '_']
    return $ UIdent (a:rest)

pIdent :: Parser Ident
pIdent = label "parse an Ident" $ do
    a <- lowerChar
    rest <- many $ choice [letterChar,char '_']
    return $ Ident (a:rest)

pSymbol :: Text -> Parser Text
pSymbol = Lexer.symbol pSpace

pSpace :: Parser ()
pSpace = Lexer.space 
           (void spaceChar) 
           (Lexer.skipLineComment "--") 
           (Lexer.skipBlockComment "{-" "-}")

这可能会让人不知所措,所以让我解释一些关键点。我知道我在开括号上可以匹配很多不同的构造,所以我将这些解析器包装在try中,这样,如果它们失败了,我可以尝试下一个可能占用开括号的解析器。也许我过多使用了try?是否会严重影响性能以回溯?

我还试图通过定义一些术语和一个运算符表来制作一个表达式解析器。现在,您可以看到我已经注释掉了运算符(功能箭头)。 由于代码现在看起来很象,当我尝试解析函数类型时,我会无限循环。我认为这可能是由于以下事实:当我尝试解析函数类型(从pType调用)时,我立即尝试解析表示函数域的类型,该类型再次调用pType。我将如何正确执行此操作?

如果我决定改用运算符表,而不是对函数类型使用自定义解析器,则会使用错误的优先级来解析事物。例如,Maybe a -> b被解析为Maybe (a -> b),而我希望它被解析为(Maybe a) -> b有没有一种方法可以使用运算符表,并且使类型构造函数的绑定比功能箭头更紧密

最后,当我正在学习megaparsec时,如果有人看到任何误解或感到困惑/意外的事情,请告诉我。我已经阅读了this教程的大部分内容,以帮助我理解这一点。

请告知我为提高问题质量所做的任何修改!

解决方法

您的代码根本不处理优先级,因此,它使用循环的左递归。

在您的代码中举例说明left-recursionpFunctionType调用pType作为第一个动作,而调用pFunctionType作为第一个动作。显然这是一个循环。

关于优先级,我建议看一下“递归下降运算符解析”教程,通过Google的快速搜索可以发现其中有几个。不过,我可以在这里总结要点。我写一些代码。

{-# language OverloadedStrings #-}

import Control.Monad.Identity
import Data.Text (Text)
import Data.Void
import Text.Megaparsec
import Text.Megaparsec.Char
import qualified Text.Megaparsec.Char.Lexer as Lexer

type Parser a = ParsecT Void Text Identity a

newtype Ident  = Ident String deriving Show
newtype UIdent = UIdent String deriving Show

data Type
    = TVar Ident
    | TFun Type Type       -- instead of "TLam"
    | TAdt UIdent [Type]
    | TTup [Type]
    | TUnit                -- instead of "TNil"
    | TBool
    | TInt
    | TFloat
    deriving Show

pSymbol :: Text -> Parser Text
pSymbol = Lexer.symbol pSpace

pChar :: Char -> Parser ()
pChar c = void (char c <* pSpace)

pSpace :: Parser ()
pSpace = Lexer.space
           (void spaceChar)
           (Lexer.skipLineComment "--")
           (Lexer.skipBlockComment "{-" "-}")

keywords :: [String]
keywords = ["Bool","Int","Float"]

pUIdent :: Parser UIdent
pUIdent = try $ do
    a <- upperChar
    rest <- many $ choice [letterChar,digitChar,char '_']
    pSpace
    let x = a:rest
    if elem x keywords
      then fail "expected an ADT name"
      else pure $ UIdent x

pIdent :: Parser Ident
pIdent = try $ do
    a <- lowerChar
    rest <- many $ choice [letterChar,char '_']
    pSpace
    return $ Ident (a:rest)

让我们在这里停止。

  • 我更改了Type中的构造函数的名称,以使其与在Haskell中的调用方式一致。我还删除了Type上的参数,以减少噪音,但您当然可以重新添加。
  • 请注意已更改的pUIdent和添加的keywords。通常,如果您想解析标识符,则必须将它们与关键字区别开。在这种情况下,Int可以解析为Int和大写标识符,因此我们必须指定Int不是标识符。 li>

继续:

pClosed :: Parser Type
pClosed =
      (TInt   <$  pSymbol "Int")
  <|> (TBool  <$  pSymbol "Bool")
  <|> (TFloat <$  pSymbol "Float")
  <|> (TVar   <$> pIdent)
  <|> (do pChar '('
          ts <- sepBy1 pFun (pChar ',') <* pChar ')'
          case ts of
            []  -> pure TUnit
            [t] -> pure t
            _   -> pure (TTup ts))

pApp :: Parser Type
pApp = (TAdt <$> pUIdent <*> many pClosed)
   <|> pClosed

pFun :: Parser Type
pFun = foldr1 TFun <$> sepBy1 pApp (pSymbol "->")

pExpr :: Parser Type
pExpr = pSpace *> pFun <* eof

我们必须根据绑定强度对运算符进行分组。对于每种强度,我们都需要有一个单独的解析函数来解析该强度的所有运算符。在这种情况下,我们具有pFunpApppClosed的粘合强度增强顺序。 pExpr只是一个包装器,用于处理顶级表达式,并处理前导空格并匹配输入的结尾。

在编写运算符解析器时,首先要确定的是封闭表达式组。封闭表达式由左右两侧的关键字或符号分隔。从概念上讲,这是“无限”的绑定强度,因为此类表达式之前和之后的文本根本不会更改其解析。

关键字和变量显然是封闭的,因为它们由单个标记组成。我们还有另外三种封闭的情况:单位类型,元组和带括号的表达式。由于所有这些都以(开头,因此我factor出来了。之后,我们将一个或多个类型用,分隔开来,我们必须分支已解析类型的数量。

优先级解析的规则是,在解析给定强度的运算符表达式时,在读取运算符之间的表达式时,我们总是调用 nextstrong 表达式解析器。

,是最弱的运算符,因此我们将函数称为第二弱的运算符pFun

pFun依次调用pApp,后者读取ADT应用程序,或回退到pClosed。在pFun中,您还可以看到权限关联性的处理,因为我们使用foldr1 TFun来组合表达式。在左关联的中缀运算符中,我们改用foldl1

请注意,解析器函数也始终会解析所有更强的表达式。因此,pFun在没有pApp时退回到->(因为sepBy1接受没有分隔符的情况),而pApp退回到{{1 }}没有ADT申请。

版权声明:本文内容由互联网用户自发贡献,该文观点与技术仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请发送邮件至 dio@foxmail.com 举报,一经查实,本站将立刻删除。

相关推荐


依赖报错 idea导入项目后依赖报错,解决方案:https://blog.csdn.net/weixin_42420249/article/details/81191861 依赖版本报错:更换其他版本 无法下载依赖可参考:https://blog.csdn.net/weixin_42628809/a
错误1:代码生成器依赖和mybatis依赖冲突 启动项目时报错如下 2021-12-03 13:33:33.927 ERROR 7228 [ main] o.s.b.d.LoggingFailureAnalysisReporter : *************************** APPL
错误1:gradle项目控制台输出为乱码 # 解决方案:https://blog.csdn.net/weixin_43501566/article/details/112482302 # 在gradle-wrapper.properties 添加以下内容 org.gradle.jvmargs=-Df
错误还原:在查询的过程中,传入的workType为0时,该条件不起作用 &lt;select id=&quot;xxx&quot;&gt; SELECT di.id, di.name, di.work_type, di.updated... &lt;where&gt; &lt;if test=&qu
报错如下,gcc版本太低 ^ server.c:5346:31: 错误:‘struct redisServer’没有名为‘server_cpulist’的成员 redisSetCpuAffinity(server.server_cpulist); ^ server.c: 在函数‘hasActiveC
解决方案1 1、改项目中.idea/workspace.xml配置文件,增加dynamic.classpath参数 2、搜索PropertiesComponent,添加如下 &lt;property name=&quot;dynamic.classpath&quot; value=&quot;tru
删除根组件app.vue中的默认代码后报错:Module Error (from ./node_modules/eslint-loader/index.js): 解决方案:关闭ESlint代码检测,在项目根目录创建vue.config.js,在文件中添加 module.exports = { lin
查看spark默认的python版本 [root@master day27]# pyspark /home/software/spark-2.3.4-bin-hadoop2.7/conf/spark-env.sh: line 2: /usr/local/hadoop/bin/hadoop: No s
使用本地python环境可以成功执行 import pandas as pd import matplotlib.pyplot as plt # 设置字体 plt.rcParams[&#39;font.sans-serif&#39;] = [&#39;SimHei&#39;] # 能正确显示负号 p
错误1:Request method ‘DELETE‘ not supported 错误还原:controller层有一个接口,访问该接口时报错:Request method ‘DELETE‘ not supported 错误原因:没有接收到前端传入的参数,修改为如下 参考 错误2:cannot r
错误1:启动docker镜像时报错:Error response from daemon: driver failed programming external connectivity on endpoint quirky_allen 解决方法:重启docker -&gt; systemctl r
错误1:private field ‘xxx‘ is never assigned 按Altʾnter快捷键,选择第2项 参考:https://blog.csdn.net/shi_hong_fei_hei/article/details/88814070 错误2:启动时报错,不能找到主启动类 #
报错如下,通过源不能下载,最后警告pip需升级版本 Requirement already satisfied: pip in c:\users\ychen\appdata\local\programs\python\python310\lib\site-packages (22.0.4) Coll
错误1:maven打包报错 错误还原:使用maven打包项目时报错如下 [ERROR] Failed to execute goal org.apache.maven.plugins:maven-resources-plugin:3.2.0:resources (default-resources)
错误1:服务调用时报错 服务消费者模块assess通过openFeign调用服务提供者模块hires 如下为服务提供者模块hires的控制层接口 @RestController @RequestMapping(&quot;/hires&quot;) public class FeignControl
错误1:运行项目后报如下错误 解决方案 报错2:Failed to execute goal org.apache.maven.plugins:maven-compiler-plugin:3.8.1:compile (default-compile) on project sb 解决方案:在pom.
参考 错误原因 过滤器或拦截器在生效时,redisTemplate还没有注入 解决方案:在注入容器时就生效 @Component //项目运行时就注入Spring容器 public class RedisBean { @Resource private RedisTemplate&lt;String
使用vite构建项目报错 C:\Users\ychen\work&gt;npm init @vitejs/app @vitejs/create-app is deprecated, use npm init vite instead C:\Users\ychen\AppData\Local\npm-