scheme COMPILER = 
class
  type
    Prog == mk_Prog(stmt : Stmt),  

    Stmt ==  
      mk_Asgn(ide : Identifier,  expr : Expr) |   
      mk_If(cond : Expr,  s1 : Stmt, s2 : Stmt) |   
      mk_Seq(head : Stmt,  last : Stmt),  

    Expr == 
      mk_Const(const : Int) |  
      mk_Plus(fst : Expr,  snd : Expr) |
      mk_Id(ide : Identifier),
    Identifier = Text

type /* storage for program variables */
  `Sigma = Identifier -m-> Int 

value     
  m : Prog -> `Sigma -> `Sigma
  m(p)(`sigma)  is  m(stmt(p))(`sigma),  

  m : Stmt -> `Sigma -> `Sigma
  m(s)(`sigma)  is  
    case s of 
      mk_Asgn(i, e) -> `sigma !! [i +> m(e)(`sigma)], 
      mk_Seq(s1, s2) -> m(s2)(m(s1)(`sigma)), 
      mk_If(c, s1, s2) -> 
        if m(c)(`sigma) ~= 0 then m(s1)(`sigma) else m(s2)(`sigma) end      
    end,
  
  m : Expr -> `Sigma -> Int
  m(e)(`sigma)  is  
    case e of 
      mk_Const(n) -> n, 
      mk_Plus(e1, e2) -> m(e1)(`sigma) + m(e2)(`sigma), 
      mk_Id(id) -> if id isin  dom `sigma then `sigma(id) else 0 end 
    end 

type
  MProg = Inst-list,
  Inst == 
     mk_Push(ide1 : Identifier) | 
     mk_Pop(Unit) | 
     mk_Add(Unit) | 
     mk_Cnst(val : Int) | 
     mk_Store(ide2 : Identifier) | 
     mk_Jumpfalse(off1 : Int) | 
     mk_Jump(off2 : Int) 


/* An interpreter for SMALL instructions */

type  Stack = Int-list
value 
  I : MProg >< Int >< Stack -> (`Sigma ->`Sigma)
  I(mp, pc, s)(`sigma) is 
    if pc <= 0 \/ pc > len mp then `sigma else
      case  mp(pc) of
        mk_Push(x) -> if x isin dom `sigma 
          then I(mp, pc + 1, <.`sigma(x).> ^ s)(`sigma)
          else  I(mp, pc + 1, <.0.> ^ s)(`sigma) end,
        mk_Pop(()) -> if len s = 0 then `sigma
          else I(mp, pc + 1, tl s)(`sigma) end,
        mk_Cnst(n)  -> I(mp, pc + 1, <.n.> ^  s)(`sigma),
        mk_Add(()) -> if len s < 2 then `sigma 
          else  I(mp, pc + 1,<.s(1) + s(2).> ^ tl tl s)(`sigma) end,
        mk_Store(x) -> if len s = 0 then `sigma
          else I(mp, pc + 1, s)(`sigma !! [x +> s(1)]) end,
        mk_Jumpfalse(n) -> if len s = 0 then `sigma
          elsif  hd s ~= 0  then I(mp, pc + 1, s)(`sigma) 
          else I(mp, pc + n, s)(`sigma) end,
        mk_Jump(n) -> I(mp, pc + n, s)(`sigma) 
      end
    end  

value
  comp_Prog : Prog -> MProg
  comp_Prog(p) is comp_Stmt(stmt(p)),

  comp_Stmt : Stmt -> MProg
  comp_Stmt(s) is
    case s of
      mk_Asgn(id, e) -> comp_Expr(e) ^ <. mk_Store(id), mk_Pop() .>,
      mk_Seq(s1, s2) -> comp_Stmt(s1) ^ comp_Stmt(s2),
      mk_If(e, s1, s2) -> 
       let 
         ce = comp_Expr(e), 
         cs1 = comp_Stmt(s1), cs2 = comp_Stmt(s2) 
       in
           ce ^ 
           <. mk_Jumpfalse(len cs1 + 3) .> ^
           <. mk_Pop() .> ^
           cs1 ^
           <. mk_Jump(len cs2 + 2) .> ^
           <. mk_Pop() .> ^
           cs2
       end
    end,

  comp_Expr : Expr -> MProg
  comp_Expr(e) is 
    case e of
      mk_Const(n) -> <. mk_Cnst(n) .>,
      mk_Plus(e1, e2) -> 
        comp_Expr(e1) ^ comp_Expr(e2) ^ <. mk_Add() .>,
      mk_Id(id) -> <. mk_Push(id) .>
    end

end
