这是家庭作业问题。
我的目标是将类型表达式以“形式转换为CPU指令列表。给定数据结构
data Expr = Const Double | Var String | Add Expr Expr | Mult Expr Expr
data Instr = LoadImmediate RegisterNumber -- put value here
RegisterValue -- the value
| Addition RegisterNumber -- put result here
RegisterNumber -- first thing to add
RegisterNumber -- second thing to multiply
| Multiply RegisterNumber -- put result here
RegisterNumber -- first thing to multiply
RegisterNumber -- second thing to multiply
type RegisterNumber = Int
type RegisterValue = Double
Type Expression有四个主要功能
Const
:将一些double类型转换为一个类型表达式,让你使用它
Var
:基本上将字符串(即“x”)转换为类型表达式,以便将其应用于常量
然后是Add
和Mult
命令,允许您添加和乘以两个类型表达式
我们可以假设我们将看到的唯一变量是“x”并且它已经在寄存器2中。结果将到达寄存器1.总共有4个寄存器。
所以Add Const 1 (Mult (Var "x") (Const 2))
在类型[Instr]中将是
[LoadImmediate 3 1,
LoadImmediate 4 2,
Multiply 1 4 2,
Addition 1 3 1]
编辑:对不起,我要提一下,因为这是一门初学者课程,我们只需要考虑表格的表达方式
(Const a) `Add` ((Var "x") `Mult` Expr)
其中'Expr'是一些表达式。或者以数学形式a0 + x(a1 + x(a2 + x ...))
我修改了我的代码,现在我得到的错误是“不在范围内:数据构造函数'RegNum'”
expr2Code :: Expr -> [Instr]
expr2Code expr = e2C 1 expr
e2C:: Int -> Expr -> Instr
e2C RegNum (Const y) = [LoadImmediate RegNum y]
e2C RegNum (Var "x") = []
e2C RegNum (Add expr1 expr2) = e2C 3 expr1 ++ e2C 4 expr2 ++ [Addition RegNum 3 4]
e2C RegNum (Mult expr1 expr2) = e2C 3 expr1 ++ e2C 4 expr2 ++ [Multiply RegNum 3 4]
很抱歉这篇长篇文章,我们将不胜感激。
答案 0 :(得分:3)
嗯,我假设你有无限数量的寄存器。如果没有,你可以体验注册溢出的快乐,但是你需要更多的指令来处理动态内存。
有三种直接的方法可以做到这一点
前两个提供了更容易优化寄存器使用的机会,但不是,但涉及中间语言。因为我们很懒,所以我们做3。
import Control.Monad.State
type Gensym = State Int
gensym :: Gensym RegisterNumber
gensym = modify (+1) >> fmap (subtract 1) get
现在我们有了一种独特的寄存器生成方式,我们可以做出非常低效的方法。
withRegister :: (RegisterNumber -> [Instr]) -> Gensym (RegisterNumber, [Instr])
withRegister f = gensym >>= \r -> return (r, f r)
compile :: Expr -> Gensym (RegisterNumber, [Instr])
compile (Const d) = withRegister $ \r -> [LoadImmediate r d]
compile (Var "x") = return (2, [])
compile (Add e1 e2) = do
(t1, c1) <- compile e1 -- Recursively compile
(t2, c2) <- compile e2 -- Each subexpression like you were
withRegister $ \r -> Addition t1 t2 r : (c1 ++ c2) -- Make sure we
-- use a unique register
compile (Mult e1 e2) = do
(t1, c1) <- compile e1
(t2, c2) <- compile e2
withRegister $ \r -> Multiply t1 t2 r : (c1 ++ c2)
compileExpr :: Expr -> [Instr]
compileExpr = reverse . snd . flip evalState 3 . compile
这基本上递归地编译每个表达式,将各种代码块连接在一起。这类似于你所拥有的,但有3个主要区别,
我正在使用monad为我处理寄存器。 你必须确保你永远不会破坏你需要的寄存器,使用monad我确保我使用的所有寄存器都是唯一的。这实在是效率低下,但非常正确。
在处理Var
时,我只是加载寄存器2中的任何内容,因为LoadImmediate
想要一个double,而你没有机制来实际绑定表达式中的变量。
x + y * z
了。这就是为什么如果你看一下Add
或Mult
的代码,每个子表达式都会编译成一个新的寄存器。您的示例生成
[LoadImmediate 4 2.0,Multiply 2 4 5,LoadImmediate 3 1.0,Addition 3 5 6]
哪个是正确的,它会乘以4和x
,然后加3。
答案 1 :(得分:0)
e2C _ (Var "x") = LoadImmediate 2 "x"
如果x
已经在寄存器2中,则根本不需要加载它。 Var "x"
不会转换为任何加载操作。相反,它在某些其他操作(加法或乘法)中转换为2
的操作数。例如,(Add (Const 25) (Var "x"))
会转换为[LoadImmediate 3 25, Addition 1 3 2]
。
e2C _ (Mult x y) = Multiply 4 (e2C _ x) (e2C _ y)
这当然不起作用,因为Multiply
的操作数是寄存器,而不是指令。您必须翻译x
并记下结果所在的寄存器rx
;然后翻译y
并注意哪个注册ry
其结果;确保rx != xy
;最后,发出Multiply rz rx ry
。
现在,如何确定rz
,以及如何确定rx != ry
?一个简单的策略是确保每个结果都进入自己的寄存器(假设它们的数量无限,这对于真正的机器架构当然不是这样)。
顶级结果将转到注册1。