我被困在用 Haskell 编写解析器的问题上,希望有人能提供帮助!
它比我通常的解析器要复杂一些,因为有两层解析。首先将语言定义解析为 AST,然后将该 AST 转换为解析实际语言的另一个解析器。
到目前为止,我已经取得了不错的进展,但我坚持在语言定义中实现递归。由于语言定义从 AST 转换为递归函数中的解析器,因此如果它还不存在,我无法弄清楚它如何调用自身。
我发现解释我的问题有点困难,所以也许举个例子会有所帮助。
语言定义可能定义一种语言由三个按顺序排列的关键字组成,然后是括号中的可选递归。
A B C ($RECURSE)
这将被解析为 AST,如:
[Keyword "A", Keyword "B", Keyword "C", Optional (Many [Recurse])]
这个Many
例子并不是真正需要的,但在我的实际项目中,可选块中可以有多个语法元素,因此 anOptional
将包含 aMany
和n 个元素。
然后我希望它被转换为解析字符串的解析器,例如:
A B C
A B C (A B C)
A B C (A B C (A B C))
我已将我的项目简化为最简单的示例。您可以看到我的 TODO 评论,我在尝试实现递归时遇到了困难。
{-# LANGUAGE OverloadedStrings #-}
module Example
( runExample,
)
where
import Control.Applicative hiding (many, some)
import Data.Text (Text)
import Data.Void
import System.IO as SIO
import Text.Megaparsec hiding (State)
import Text.Megaparsec.Char (space1, string')
import qualified Text.Megaparsec.Char.Lexer as L
import Text.Megaparsec.Debug
import Text.Pretty.Simple (pPrint)
-- Types
type Parser = Parsec Void Text
data SyntaxAst = Keyword Text | Recurse | Optional SyntaxAst | Many [SyntaxAst]
-- Megaparsec Base Parsers
-- Space consumer - used by other parsers to ignore whitespace
sc :: Parser ()
sc =
L.space
space1
(L.skipLineComment "--")
(L.skipBlockComment "/*" "*/")
-- Runs a parser, then consumes any left over space with sc
lexeme :: Parser a -> Parser a
lexeme = L.lexeme sc
-- Parses a string, then consumes any left over space with sc
symbol :: Text -> Parser Text
symbol = L.symbol sc
-- Parses something between parentheses
inParens :: Parser a -> Parser a
inParens =
between
(symbol "(")
(symbol ")")
-- Transforms the AST into a parser
transformSyntaxExprToParser :: SyntaxAst -> Parser [Text]
transformSyntaxExprToParser (Many exprs) = dbg "Many" (createParser exprs)
transformSyntaxExprToParser (Keyword text) = dbg "Keyword" (pure <$> lexeme (string' text))
transformSyntaxExprToParser (Optional inner) = dbg "Optional" (option [] (try (inParens (transformSyntaxExprToParser inner))))
transformSyntaxExprToParser Recurse = dbg "Recurse" (pure ["TODO"]) -- TODO: How do I recurse here?
-- transformSyntaxExprToParser s Recurse = dbg "Recurse" (createParser s) -- Seems to work in the example, but in my actual application creates an infinite loop and freezes
-- Walks over the parser AST and convert it to a parser
createParser :: [SyntaxAst] -> Parser [Text]
createParser expressions =
do
foldr1 (liftA2 (<>)) (fmap transformSyntaxExprToParser expressions)
runExample :: IO ()
runExample = do
-- To make the example simple, lets cut out the language definition parsing and just define
-- it literally.
let languageParser = createParser [Keyword "A", Keyword "B", Keyword "C", Optional (Many [Recurse])]
let run p = runParser p "" "A B C (A B C (A B C))"
let result = run languageParser
case result of
Left bundle -> SIO.putStrLn (errorBundlePretty bundle)
Right xs -> pPrint xs
我尝试过的几件事:
- 将原始 AST 传递给
transformSyntaxExprToParser
函数并createParser
在Recurse
遇到令牌时调用。由于无限循环,这不起作用。 - 使用可变引用(如 IORef/STRef)传入一个引用,该引用在转换完成后更新为引用最终解析器。我不知道如何将 IO/ST monad 线程化到解析器转换函数中。
- 状态单子。我不知道如何通过 state monad 传递引用。
我希望这是有道理的,如果我需要详细说明,请告诉我。如果有帮助,我也可以推进我的整个项目。
谢谢阅读!
编辑:我对我的原始示例进行了更改,以在https://pastebin.com/DN0JJ9BA上演示无限循环问题(整合下面答案中的优秀建议)