let rec print_expr e =
    let print t = function
        CTTexpComma( e1, e2 ) ->
          "("^(print_expr e1)^", "^(print_expr e2)^") : "^(print_c_type t)
      | CTTexpAssign( e1, e2 ) ->
          "("^(print_expr e1)^" = "^(print_expr e2)^") : "^(print_c_type t)
      | CTTexpBinAssign( o, e1, None, e2 ) ->
          "("^(print_expr e1)^" "^(print_binary_operator o)^"= "^(print_expr e2)^") : "^(print_c_type t)
      | CTTexpBinAssign( o, e1, Some t2, e2 ) ->
          "("^(print_expr e1)^" "^(print_binary_operator o)^"= ("^(print_c_type t2)^")"^(print_expr e2)^") : "^(print_c_type t)

      | CTTexpConditional( e1, e2, e3 ) ->
          "("^(print_expr e1)^" ? "^(print_expr e2)^" : "^(print_expr e3)^") : "^(print_c_type t)
      | CTTexpBinExpr( o, e1, e2 ) ->
          "("^(print_expr e1)^" "^(print_binary_operator o)^" "^(print_expr e2)^") : "^(print_c_type t)
      | CTTexpCoerce( t, e ) -> 
          "("^(print_c_type t)^")"^(print_expr e)
      | CTTexpUnaryExpr( o, e ) ->
          "("^(print_unary_operator o)^" "^(print_expr e)^") : "^(print_c_type t)
      | CTTexpAddress e -> "&"^(print_expr e)^" : "^(print_c_type t)
      | CTTexpPtrDeref e -> "*"^(print_expr e)^" : "^(print_c_type t)
      | CTTexpInvoke( e, l ) ->
          let rec continue = function
              [] -> ""
            | [e] -> print_expr e
            | e::l -> (print_expr e)^", "^(continue l)
          in
          (print_expr e)^"("^(continue l)^")"^" : "^(print_c_type t)
      | CTTexpField( e, s ) ->
          (print_expr e)^"."^s^" : "^(print_c_type t)
      | CTTexpConstant c ->
          (print_c_constants c)^" : "^(print_c_type t)
      | CTTexpVar s ->
          s^" : "^(print_c_type t)
    in
    print e.expr_type e.expr_t