let rec pp_expr depth = function
    | Id(id) -> Printf.sprintf "Id(%s)" id
    | This -> "This"
    | Null -> "Null"
    | NewObj(the_type, args) -> Printf.sprintf("\n%sNewObj(%s, %s)") (indent depth) the_type (pp_str_list (pp_expr depth) args depth)
    | Anonymous(the_type, args, body) -> Printf.sprintf("\n%sAnonymous(%s, %s, %s)") (indent depth) the_type (pp_str_list (pp_expr depth) args depth) (pp_str_list (pp_func_def depth) body depth)
    | Literal(l) -> Printf.sprintf "\n%sLiteral(%s)" (indent depth) (pp_lit l)
    | Invoc(receiver, meth, args) -> Printf.sprintf "\n%sInvocation(%s, %s, %s)" (indent depth) ((pp_expr (depth+1)) receiver) meth (pp_str_list (pp_expr (depth+1)) args depth)
    | Field(receiver, field) -> Printf.sprintf "\n%sField(%s, %s)" (indent depth) ((pp_expr depth) receiver) field
    | Deref(var, index) -> Printf.sprintf "\n%sDeref(%s, %s)" (indent depth) ((pp_expr depth) var) ((pp_expr depth) var)
    | Unop(an_op, exp) -> Printf.sprintf "\n%sUnop(%s, %s)" (indent depth) (pp_op an_op) ((pp_expr depth) exp)
    | Binop(left, an_op, right) -> Printf.sprintf "\n%sBinop(%s, %s, %s)" (indent depth) (pp_op an_op) ((pp_expr depth) left) ((pp_expr depth) right)
    | Refine(fname, args, totype) -> Printf.sprintf "Refine(%s, %s, %s)" fname (pp_str_list (pp_expr (depth+1)) args (depth+1)) (pp_opt _id  totype)
    | Assign(the_var, the_expr) -> Printf.sprintf "\n%sAssign(%s, %s)" (indent depth) ((pp_expr (depth+1)) the_var) ((pp_expr (depth+1)) the_expr)
    | Refinable(the_var) -> Printf.sprintf "\n%sRefinable(%s)" (indent depth) the_var
and pp_var_def depth (the_type, the_var) = Printf.sprintf "\n%s(%s, %s)" (indent depth) the_type the_var
and pp_stmt depth = function
    | Decl(the_def, the_expr) -> Printf.sprintf "\n%sDecl(%s, %s)" (indent depth) ((pp_var_def (depth+1)) the_def) (pp_opt (pp_expr depth) the_expr)
    | If(clauses) -> Printf.sprintf "\n%sIf(%s)" (indent depth) (pp_str_list (inspect_clause depth) clauses depth)
    | While(pred, body) -> Printf.sprintf "\n%sWhile(%s, %s)" (indent depth) ((pp_expr depth) pred) (pp_str_list (pp_stmt (depth+1)) body depth)
    | Expr(the_expr) -> Printf.sprintf "\n%sExpr(%s)" (indent depth) ((pp_expr (depth+1)) the_expr)
    | Return(the_expr) -> Printf.sprintf "\n%sReturn(%s)" (indent depth) (pp_opt (pp_expr depth) the_expr)
    | Super(args) -> Printf.sprintf "\n%sSuper(%s)" (indent depth) (pp_str_list (pp_expr depth) args depth)
and inspect_clause depth (opt_expr, body) = Printf.sprintf "(%s, %s)" (pp_opt (pp_expr depth) opt_expr) (pp_str_list (pp_stmt (depth+1)) body depth)
and class_section = function
    | Publics  -> "Publics"
    | Protects -> "Protects"
    | Privates -> "Privates"
    | Refines  -> "Refines"
    | Mains    -> "Mains"
and pp_func_def depth func = Printf.sprintf "\n%s{\n%sreturns = %s,\n%shost = %s,\n%sname = %s,\n%sstatic = %B,\n%sformals = %s,\n%sbody = %s,\n%ssection = %s,\n%sinklass = %s,\n%suid = %s\n%s}"
    (indent (depth-1))
    (indent depth)
    (pp_opt _id func.returns)
    (indent depth)
    (pp_opt _id func.host)
    (indent depth)
    func.name
    (indent depth)
    func.static
    (indent depth)
    (pp_str_list (pp_var_def (depth+1)) func.formals depth)
    (indent depth)
    (pp_str_list (pp_stmt (depth+1)) func.body depth)
    (indent depth)
    (class_section func.section)
    (indent depth)
    func.inklass
    (indent depth)
    func.uid
    (indent (depth-1))