在Haskell中编写汇编程序 - mapM与状态?

时间:2017-11-18 23:20:13

标签: haskell monads monad-transformers

我在Haskell写了一个非常简单的两遍汇编程序,而且我遇到了一个我还没有解决过的经验的场景。我认为解决方案很可能涉及monad变压器,我并不理解。

汇编程序将汇编代码解析为Statement的列表,它们是指令或标签。有些Statement可能会引用标签。汇编程序需要将Statement转换为Instruction s,这涉及消除标签并用适当的值替换标签引用。

我编写了汇编程序的第一个传递,它产生一个[(String, Int)]表示从标签到地址的映射。我还编写了以下函数,用于将Statement翻译成Instruction

stmtToInstruction :: Int -> [(String, Int)] -> Statement -> Either String [I.Instruction]
stmtToInstruction addr labels stmt = case stmt of
    ADD d s1 s2     -> Right [I.ADD d s1 s2]
    BEQL s1 s2 l    -> case do label <- find (\e -> fst e == l) labels
                               let labelAddr = snd label
                               let relativeAddr = I.ImmS $ fromIntegral (labelAddr - addr)
                               return (I.BEQ s1 s2 relativeAddr) of
                        Just i -> Right [i]
                        Nothing -> Left $ "Label " ++ l ++ " not defined"
    LABEL _         -> Right []

为简洁起见,我省略了几个案例,但您可以在此处看到所有可能的结果:

  • ADD总是成功并产生指令
  • BEQL可以成功或失败,具体取决于是否找到标签
  • LABEL总是成功,即使它没有产生实际指示

这可以按预期工作。我现在遇到的问题是写这个函数:

replaceLabels :: [Statement] -> Either String [I.Instruction]

replaceLabels获取一个语句列表,并在每个语句上运行stmtToInstructionaddr的{​​{1}}参数必须是到目前为止累计的stmtToInstruction的长度。如果其中一个标签引用无效,则输出可以是[Instruction],如果没有错误,则输出可以是Left String

Right [I.Instruction]为我们提供了一些方法,但无法将当前地址注入mapM :: Monad m => (a -> m b) -> [a] -> m [b]函数。我如何使这项工作?

3 个答案:

答案 0 :(得分:4)

你是对的:StateT monad变换器可以解决这个问题:

imapM :: (Traversable t, Monad m)
          => (Int -> a -> m b) -> t a -> m (t b)
imapM f = flip runStateT 0 .
  mapM (\a ->
    do
      count <- get
      put $! count + 1
      f count a)

但为列表编写专用版可能更好:

itraverse :: Applicative f
          => (Int -> a -> f b) -> [a] -> f [b]
itraverse f = go 0 where
  go !_ [] = pure []
  go !count (x:xs) = (:) <$> f count x <*> go (count + 1) xs

答案 1 :(得分:1)

我已经实施了一个递归解决方案,我确信效率非常低。我仍然有兴趣看到正确的&#39;这样做的方式。

replaceLabels :: [Statement] -> Either String [I.Instruction]
replaceLabels [] = Right []
replaceLabels stmts@(s:ss) = replaceLabels' labels stmts 0
    where labels = process stmts

replaceLabels' :: [(String, Int)] -> [Statement] -> Int -> Either String [I.Instruction]
replaceLabels' _ [] _ = Right []
replaceLabels' labels (s:ss) addr = do
                                instructions <- stmtToInstruction addr labels s
                                restInstructions <- replaceLabels' labels ss (addr + length instructions)
                                return (instructions ++ restInstructions)

答案 2 :(得分:0)

我首先要改变

stmtToInstruction :: Int -> [(String, Int)] -> Statement -> Either String [I.Instruction]

stmtToInstruction :: [(String, Int)] -> Statement -> Either String (Int -> [I.Instruction])

即将移动地址的函数移动到Right的{​​{1}}分支。原因是标签引用错误似乎与地址无关,因此最好先处理引用错误然后再单独担心地址内容。

此函数解析引用:

Either

(traverse相当于resolveRefs :: [(String,Int)] -> [Statement] -> Either String [Int -> [Instruction]] resolveRefs environment = traverse (stmtToInstruction environment) ,但只需要mapM约束。它们仅仅是出于历史原因而具有不同的功能。)

好的,在处理完错误后,现在让我们关注Applicative列表。似乎我们必须从左边映射它,同时携带我们必须提供给每个功能的累积地址。 mapAccumL函数非常适用于此:

[Int -> [Instruction]]