我在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
获取一个语句列表,并在每个语句上运行stmtToInstruction
。 addr
的{{1}}参数必须是到目前为止累计的stmtToInstruction
的长度。如果其中一个标签引用无效,则输出可以是[Instruction]
,如果没有错误,则输出可以是Left String
。
Right [I.Instruction]
为我们提供了一些方法,但无法将当前地址注入mapM :: Monad m => (a -> m b) -> [a] -> m [b]
函数。我如何使这项工作?
答案 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]]