Megaparsec:解析期间的宏扩展

时间:2016-09-10 14:14:13

标签: haskell parsec megaparsec

在一个小型DSL中,我正在解析宏定义,类似于#define C预处理器指令(这里是一个简单的例子):

_def mymacro(a,b) = a + b / a

解析器遇到以下调用时

c = mymacro(pow(10,2),3)

它扩展到

c = pow(10,2) + 3 / pow(10,2)

我目前的做法是:

  • 将解析器包装在State monad中
  • 解析宏定义时,将它们存储在状态中,其主体未解析(将其解析为字符串)
  • 解析宏调用时,在状态中查找定义,替换正文中的参数,用此主体替换调用并继续解析。

最后一步的一些代码:

macrocallStmt
  = do  -- capture starting position and content of old input before macro call
        oldInput <- getInput
        oldPos <- getPosition
        -- parse the call
        ret <- identifier
        symbolCS "="
        i <- identifier
        args <- parens $ commaSep anyExprStr
        -- expand the macro call
        us <- get
        let inlinedCall = replaceMacroArgs i args ret us
        -- set up new input with macro call expanded
        remainder <- getInput
        let newInput = T.append inlinedCall (T.cons '\n' remainder)
        setPosition oldPos
        setInput newInput
        -- update the expanded input script
        modify (updateExpandedInput oldInput newInput)

anyExprStr = fmap praShow expression <|> fmap praShow algexpr

这种方法可以很好地完成工作。但是,它有许多缺点。

多次解析

任何有效的DSL表达式都可以是宏调用的参数。因此,即使我只需要他们的文本表示(在宏体中被替换),我需要解析它们然后再将它们转换为字符串 - 只是寻找下一个逗号是行不通的。然后将解析完整和自定义的宏。所以在实践中,宏参数会被解析两次(并且还会显示,这会产生成本)。而且,每次调用都需要对(几乎相同的)主体进行新的解析。保持身体在内存中未被解析的原因是允许最大的灵活性:在体内,甚至可以用宏参数构造DSL关键字。

错误处理

因为展开的主体插入了未消耗的输入(替换调用)之前,所以初始和最终输入可能完全不同。如果发生解析错误,则展开输入中发生错误的位置可用。但是,在处理错误时,我只有原始的,而不是扩展的输入。所以错误位置不匹配。 这就是为什么在上面的代码片段中,我使用状态来保存扩展的输入,以便在解析器退出时出现错误。 这很好用,但是我注意到它变得非常昂贵,每次扩展时都会为整个流分配新的Text数组(输入流是Text)。也许在这种情况下将扩展输入保持为String而不是Text的状态会更便宜,即当需要替换中间部分时?

这个问题的原因是:

  • 我希望对上述两个问题提出建议/意见
  • 任何人都可以建议一个更好的方法吗?

0 个答案:

没有答案