我正在用 Haskell 编写一个笨拙的解释器,我想出了我认为对程序非常有趣的描述:
data Program m = Instruction (m ()) (Program m)
| Control (m (Program m))
| Halt
然而,将一个 Brainfuck 程序的文本表示解析为这种数据类型是很棘手的。尝试正确解析方括号会出现问题,因为有一些打结要做,以便Instruction
循环内的最终链接再次链接到循环Control
。
更多初步信息。有关所有详细信息,请参阅github repo 上的此版本。
type TapeM = StateT Tape IO
type TapeP = Program TapeM
type TapeC = Cont TapeP
branch :: Monad m => m Bool -> Program m -> Program m -> Program m
branch cond trueBranch falseBranch =
Control ((\b -> if b then trueBranch else falseBranch) `liftM` cond)
loopControl :: TapeP -> TapeP -> TapeP
loopControl = branch (not <$> is0)
这是我尝试过的:
toProgram :: String -> TapeP
toProgram = (`runCont` id) . toProgramStep
liftI :: TapeM () -> String -> TapeC TapeP
liftI i cs = Instruction i <$> toProgramStep cs
toProgramStep :: String -> TapeC TapeP
toProgramStep ('>':cs) = liftI right cs
-- similarly for other instructions
toProgramStep ('[':cs) = push (toProgramStep cs)
toProgramStep (']':cs) = pop (toProgramStep cs)
push :: TapeC TapeP -> TapeC TapeP
push mcontinue = do
continue <- mcontinue
cont (\breakMake -> loopControl continue (breakMake continue))
pop :: TapeC TapeP -> TapeC TapeP
pop mbreak = do
break <- mbreak
cont (\continueMake -> loopControl (continueMake break) break)
我想我可以以某种方式使用延续来将信息从'['
案例传递到']'
案例,反之亦然,但我对 Cont 没有足够的把握,除了对看起来可能有效的事情进行疯狂猜测之外,我还没有真正做任何事情,如上所示push
和pop
。这编译并运行,但结果是垃圾。
这种情况可以Cont
用来打结吗?如果不是,那么我应该使用什么技术来实现toProgram
?
注 1:我之前有一个微妙的逻辑错误:loopControl = branch is0
布尔值反转了。
注意 2:我设法使用MonadFix
(如 jberryman 建议的那样)State
提出了一个解决方案(请参阅github 存储库的当前状态)。我仍然想知道如何做到这一点Cont
。
注 3:我的 Racketeer 导师为我准备了一个类似的 Racket 程序(查看所有修订版)。他的管道/管道输出技术可以使用 翻译成 HaskellCont
吗?
tl; 博士我设法使用 MonadFix 做到了这一点,而其他人则设法使用 Racket 的延续组合器做到了这一点。我很确定这可以Cont
在 Haskell 中完成。你能告诉我怎么做吗?