【问题标题】:Insert a character into parser combinator character stream in Haskell在 Haskell 中将字符插入解析器组合器字符流
【发布时间】:2013-08-20 15:12:44
【问题描述】:

这个问题与Parsec 和uu-parsinglib 有关。当我们编写解析器组合器时,它们会处理来自编译器的字符流。是否有可能解析一个字符并将其放回(或返回另一个字符)到输入流?

我想例如解析输入“test + 5”,解析t,e,s,t 并在识别test 模式后,将例如v 字符返回进入字符流,所以在继续解析过程的同时,我们将匹配v + 5

我现在不想在任何特定情况下使用它 - 我想深入了解可能性。

【问题讨论】:

  • Parsec 当然可以,因为您可以使用getInput 和setInput 函数。
  • 是的,Parsec 可以做到这一点,但它肯定会增加解析器的复杂性并使调试更加困难。它的一个常见用途是为编程语言实现预处理器。

标签: parsing haskell parsec parser-combinators uu-parsinglib


【解决方案1】:

我不确定这些解析器是否可以直接使用,但一般而言,您可以通过将解析器与允许注入剩余部分的流结合起来来实现。

例如,使用attoparsec-conduit,您可以使用以下方法将解析器转换为管道

sinkParser :: (AttoparsecInput a, MonadThrow m)
           => Parser a b -> Consumer a m b

其中Consumer 是一种特殊的管道,它不产生任何输出,只接收输入并返回最终值。

由于管道支持剩余部分,您可以创建一个辅助方法,将解析器转换为管道:

import Data.Attoparsec.Types
import Data.Conduit
import Data.Conduit.Attoparsec
import Data.Functor

reinject :: (AttoparsecInput a, MonadThrow m)
    => Parser a (Maybe a, b) -> Consumer a m b
reinject p = do
    (lo, r) <- sinkParser p
    maybe (return ()) leftover lo
    return r

然后您使用sinkParser 将标准解析器转换为管道,使用reinject 将这些特殊解析器转换为管道,然后组合管道而不是解析器。

【讨论】:

    【解决方案2】:

    我认为最简单的存档方法是构建一个多层解析器。想想词法分析器+解析器的组合。这是解决这个问题的一种干净的方法。

    你必须将这两种解析分开。搜索和替换解析转到第一个解析器,构建 AST 解析到第二个解析器。或者您可以创建一个中间令牌表示。

    import Text.Parsec
    import Text.Parsec.String
    
    parserLvl1 :: Parser String
    parserLvl1 = many (try (string "test" >> return 'v') <|> anyChar)
    
    parserLvl2 :: Parser Plus
    parserLvl2 = do text1 <- many (noneOf "+")
                    char '+'
                    text2 <- many (noneOf "+")
                    return $ Plus text1 text2
    
    data Plus = Plus String String
      deriving Show
    
    wholeParse :: String -> Either ParseError Plus
    wholeParse source = do res1 <- parse parserLvl1 "lvl1" source
                           res2 <- parse parserLvl2 "lvl2" res1
                           return res2
    

    现在您可以解析您的示例。 wholeParse "test+5" 导致 Right (Plus "v" "5")。

    可能的变化:

    • 创建一个类和一个实例以组合包装的解析器阶段。 (可能携带解析器状态。)
    • 创建一个中间表示,一个令牌流

    【讨论】:

      【解决方案3】:

      这很容易在 uu-parsinglib 中使用 pSwitch 函数完成。但问题是你为什么要这样做?因为输入中缺少 v?在这种情况下,uu-parsinglib 将自动执行纠错,因此您不需要这样的东西。否则你可以写

      pSwitch :: (st1 -> (st2, st2 -> st1)) -> P st2 a -> P st1 a
      pInsert_v = pSwitch (\st1 -> (prepend v st2, id) (pSucceed ())
      

      这取决于你的实际状态类型如何实际添加 v,所以你必须定义函数

      前置
      你自己。我不知道例如这样的插入将如何影响文件中的当前位置等。

      Doaitse Swierstra

      【讨论】:

        猜你喜欢
        • 2023-03-15
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 2015-07-12
        • 2010-09-20
        • 1970-01-01
        • 2016-02-20
        相关资源
        最近更新 更多