我正在尝试更熟悉 megaparsec,但我遇到了一些关于presedences 的问题。通过标题中的“嵌套数据”,我指的是我正在尝试解析类型,而这些类型又可能包含其他类型。如果有人可以解释为什么这不符合我的预期,请不要犹豫告诉我。
我正在尝试解析类似于 Haskell 中的类型。类型要么是基本类型Int
,要么Bool
是Float
类型变量a
(任何小写单词)。我们还可以从类型构造函数(大写单词)构造代数数据类型,例如Maybe
类型参数(任何其他类型)。例子是Maybe a
和Either (Maybe Int) Bool
。与右侧关联并用 构造的函数->
,例如Maybe a -> Either Int (b -> c)
. N 元元组是由 and 分隔,
并包含在(
and中的类型序列)
,例如(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, digitChar, 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
. 有没有一种方法可以让我使用运算符表并且仍然让类型构造函数比函数箭头绑定得更紧密?
最后,当我边走边学时,如果有人看到任何误解或奇怪/意外的事情,请告诉我。我已经阅读了本教程的大部分内容以达到这一点。
请让我知道我可以进行的任何编辑以提高我的问题的质量!