Haskell:将表达式转换为指令列表

时间:2013-11-25 15:22:47

标签: haskell expression cpu-registers

这是家庭作业问题。

我的目标是将类型表达式以“形式转换为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”)转换为类型表达式,以便将其应用于常量
然后是AddMult命令,允许您添加和乘以两个类型表达式

我们可以假设我们将看到的唯一变量是“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]

很抱歉这篇长篇文章,我们将不胜感激。

2 个答案:

答案 0 :(得分:3)

嗯,我假设你有无限数量的寄存器。如果没有,你可以体验注册溢出的快乐,但是你需要更多的指令来处理动态内存。

有三种直接的方法可以做到这一点

  1. 表达式 - > SSA - > INSTR
  2. 表达式 - > CPS - > INSTR
  3. 表达式 - > INSTR
  4. 前两个提供了更容易优化寄存器使用的机会,但不是,但涉及中间语言。因为我们很懒,所以我们做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个主要区别,

    1. 我正在使用monad为我处理寄存器。    你必须确保你永远不会破坏你需要的寄存器,使用monad我确保我使用的所有寄存器都是唯一的。这实在是效率低下,但非常正确。

    2. 在处理Var时,我只是加载寄存器2中的任何内容,因为LoadImmediate想要一个double,而你没有机制来实际绑定表达式中的变量。

    3. 因为你没有处理表达式,所以每个计算块都必须明确地卡在寄存器中。你不能再做x + y * z了。这就是为什么如果你看一下AddMult的代码,每个子表达式都会编译成一个新的寄存器。
    4. 您的示例生成

      [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。