在一个小型 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 关键字也可以从宏参数中构造出来。
错误处理
因为展开的主体插入在未使用的输入之前(替换调用),所以初始输入和最终输入可能完全不同。在解析错误的情况下,扩展中发生错误的位置输入可用。但是,在处理错误时,我只有原始输入,而不是扩展输入。所以错误位置将不匹配。这就是为什么在上面的代码片段中,我使用状态来保存扩展的输入,以便在解析器因错误退出时可用。这很好用,但我注意到它变得非常昂贵,每次扩展时都会为整个流分配新的文本数组(输入流是文本)。也许在这种情况下将扩展输入保持为字符串而不是文本会更便宜,即当需要更换中间部分时?
这个问题的原因是:
- 我将不胜感激对上述两个问题的建议/评论
- 任何人都可以提出更好的方法吗?