【问题标题】:Writing an assembler in Haskell - mapM with state?在 Haskell 中编写汇编程序 - 带状态的 mapM?
【发布时间】:2017-11-18 23:20:12
【问题描述】:

我正在用 Haskell 编写一个非常简单的两遍汇编程序,我遇到了一个我还没有经验来解决的场景。我认为解决方案很可能涉及 monad 转换器,我不太了解。

汇编器将汇编代码解析为Statements 的列表,这些列表可以是指令也可以是标签。一些Statements 可能会引用标签。汇编器需要将Statements 转换为Instructions,这包括消除标签并用适当的值替换标签引用。

我已经编写了汇编程序的第一遍,它产生了一个[(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 获取一个语句列表,并在每个语句上运行stmtToInstructionstmtToInstructionaddr 参数必须是到目前为止累积的 [Instruction] 的长度。如果标签引用之一无效,则输出可能是 Left String,如果没有错误,则输出可能是 Right [I.Instruction]

mapM :: Monad m =&gt; (a -&gt; m b) -&gt; [a] -&gt; m [b] 为我们提供了一些方法,但无法将当前地址注入(a -&gt; m b) 函数。我该如何完成这项工作?

【问题讨论】:

标签: haskell monads monad-transformers


【解决方案1】:

你是对的: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

【讨论】:

  • 你也可以使用zipWithM f [0..]实现itraverse
【解决方案2】:

我已经实现了一个递归解决方案,我确信它非常低效。我仍然有兴趣看到这样做的“正确”方式。

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)

【讨论】:

    【解决方案3】:

    我会从改变开始

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

    进入

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

    也就是说,将获取地址的函数移动到EitherRight 分支中。原因是标签引用错误似乎与地址无关,因此最好先处理引用错误,然后再单独处理地址问题。

    此函数解析引用:

    resolveRefs :: [(String,Int)] -> [Statement] -> Either String [Int -> [Instruction]]
    resolveRefs environment = traverse (stmtToInstruction environment)
    

    (traverse 等价于 mapM 但它只需要一个 Applicative 约束。它们只是出于历史原因而具有不同的功能。)

    好的,在处理完错误之后,现在让我们关注[Int -&gt; [Instruction]] 列表。似乎我们必须从左边映射它,同时携带一个我们必须提供给每个函数的累积地址。 mapAccumL 函数非常适合:

    resolveAddrs ::  [Int -> [Instruction]] -> [Instruction]
    resolveAddrs funcs = mconcat . snd $ accumulate funcs
        where
        accumulate :: [Int -> [Instruction]] -> (Int,[[Instruction]])
        accumulate = mapAccumL step 0
        step address func = let is = func address in (address + length is,is)
    

    【讨论】:

      猜你喜欢
      • 2016-06-14
      • 1970-01-01
      • 2011-07-09
      • 2011-10-14
      • 2015-06-15
      • 2011-12-13
      • 2010-10-29
      • 1970-01-01
      相关资源
      最近更新 更多