[@@@warning "-42"]
[@@@coverage exclude_file]

open AST

module Region = Simple_utils.Region
open! Region

let sprintf = Printf.sprintf

type state = <
  offsets  : bool;
  mode     : [`Point | `Byte];
  buffer   : Buffer.t;
  pad_path : string;
  pad_node : string;
  pad      : int -> int -> state
>

let mk_state ~offsets ~mode ~buffer =
  object
    method offsets  = offsets;
    method mode     = mode;
    method buffer   = buffer
    val pad_path    = ""
    method pad_path = pad_path
    val pad_node    = ""
    method pad_node = pad_node

    (** The method [pad] updates the current padding, which is
        comprised of two components: the padding to reach the new node
        (space before reaching a subtree, then a vertical bar for it)
        and the padding for the new node itself (Is it the last child
        of its parent?).
     *)
    method pad arity rank =
      {< pad_path =
           pad_node ^ (if rank = arity-1 then "`-- " else "|-- ");
         pad_node =
           pad_node ^ (if rank = arity-1 then "    " else "|   ")
      >}
  end

let compact state (region: Region.t) =
  region#compact ~offsets:state#offsets state#mode

(** {1 Printing the tokens with their source regions} *)

let print_nsepseq :
  state -> string -> (state -> 'a -> unit) ->
  ('a, Region.t) Utils.nsepseq -> unit =
  fun state sep print (head, tail) ->
    let print_aux (sep_reg, item) =
      let sep_line =
        sprintf "%s: %s\n" (compact state sep_reg) sep in
      Buffer.add_string state#buffer sep_line;
      print state item
    in print state head; List.iter print_aux tail

let print_sepseq :
  state -> string -> (state -> 'a -> unit) ->
  ('a, Region.t) Utils.sepseq -> unit =
  fun state sep print -> function
        None -> ()
  | Some seq -> print_nsepseq state sep print seq

let print_token state region lexeme =
  let line =
    sprintf "%s: %s\n"(compact state region) lexeme
  in Buffer.add_string state#buffer line

let print_var state {region; value} =
  let line =
    sprintf "%s: Ident \"%s\"\n"
            (compact state region) value
  in Buffer.add_string state#buffer line

let print_constr state {region; value} =
  let line =
    sprintf "%s: Constr \"%s\"\n"
            (compact state region) value
  in Buffer.add_string state#buffer line

let print_string state {region; value} =
  let line =
    sprintf "%s: String %s\n"
            (compact state region) value
  in Buffer.add_string state#buffer line

let print_bytes state {region; value} =
  let lexeme, abstract = value in
  let line =
    sprintf "%s: Bytes (\"%s\", \"0x%s\")\n"
            (compact state region) lexeme
            (Hex.show abstract)
  in Buffer.add_string state#buffer line

let print_int state {region; value} =
  let lexeme, abstract = value in
  let line =
    sprintf "%s: Int (\"%s\", %s)\n"
            (compact state region) lexeme
            (Z.to_string abstract)
  in Buffer.add_string state#buffer line

let print_nat state {region; value} =
  let lexeme, abstract = value in
  let line =
    sprintf "%s: Nat (\"%s\", %s)\n"
            (compact state region) lexeme
            (Z.to_string abstract)
  in Buffer.add_string state#buffer line

let rec print_tokens state ast =
  let {decl; eof} = ast in
  Utils.nseq_iter (print_decl state) decl;
  print_token state eof "EOF"

and print_attr_decl state =
  print_ne_injection state "attributes" print_string

and print_decl state = function
  TypeDecl  decl -> print_type_decl  state decl
| ConstDecl decl -> print_const_decl state decl
| FunDecl   decl -> print_fun_decl   state decl
| AttrDecl  decl -> print_attr_decl  state decl

and print_const_decl state {value; _} =
  let {kwd_const; name; colon; const_type;
       equal; init; terminator; _} = value in
  print_token      state kwd_const "const";
  print_var        state name;
  print_token      state colon ":";
  print_type_expr  state const_type;
  print_token      state equal "=";
  print_expr       state init;
  print_terminator state terminator

and print_type_decl state {value; _} =
  let {kwd_type; name; kwd_is;
       type_expr; terminator} = value in
  print_token      state kwd_type "type";
  print_var        state name;
  print_token      state kwd_is "is";
  print_type_expr  state type_expr;
  print_terminator state terminator

and print_type_expr state = function
  TProd   cartesian   -> print_cartesian   state cartesian
| TSum    sum_type    -> print_sum_type    state sum_type
| TRecord record_type -> print_record_type state record_type
| TApp    type_app    -> print_type_app    state type_app
| TFun    type_fun    -> print_type_fun    state type_fun
| TPar    par_type    -> print_par_type    state par_type
| TVar    type_var    -> print_var         state type_var
| TStringLiteral s -> print_string state s

and print_cartesian state {value; _} =
  print_nsepseq state "*" print_type_expr value

and print_variant state ({value; _}: variant reg) =
  let {constr; arg} = value in
  print_constr state constr;
  match arg with
    None -> ()
  | Some (kwd_of, t_expr) ->
      print_token     state kwd_of "of";
      print_type_expr state t_expr

and print_sum_type state {value; _} =
  print_nsepseq state "|" print_variant value

and print_record_type state record_type =
  print_ne_injection state "record" print_field_decl record_type

and print_type_app state {value; _} =
  let type_name, type_tuple = value in
  print_var        state type_name;
  print_type_tuple state type_tuple

and print_type_fun state {value; _} =
  let type_expr_a, arrow, type_expr_b = value in
  print_type_expr  state type_expr_a;
  print_token      state arrow "->";
  print_type_expr  state type_expr_b

and print_par_type state {value; _} =
  let {lpar; inside; rpar} = value in
  print_token     state lpar "(";
  print_type_expr state inside;
  print_token     state rpar ")"

and print_field_decl state {value; _} =
  let {field_name; colon; field_type} = value in
  print_var       state field_name;
  print_token     state colon ":";
  print_type_expr state field_type

and print_type_tuple state {value; _} =
  let {lpar; inside; rpar} = value in
  print_token state lpar "(";
  print_nsepseq state "," print_type_expr inside;
  print_token state rpar ")"

and print_fun_decl state {value; _} =
  let {kwd_function; fun_name; param; colon;
       ret_type; kwd_is; block_with;
       return; terminator; _} = value in
  print_token       state kwd_function "function";
  print_var         state fun_name;
  print_parameters  state param;
  print_token       state colon ":";
  print_type_expr   state ret_type;
  print_token       state kwd_is "is";
  (match block_with with
    None -> ()
  | Some (block, kwd_with) ->
     print_block state block;
     print_token state kwd_with "with");
  print_expr state return;
  print_terminator state terminator;

and print_fun_expr state {value; _} =
  let {kwd_recursive; kwd_function; param; colon;
       ret_type; kwd_is; return} : fun_expr = value in
  print_token_opt   state kwd_recursive "recursive";
  print_token       state kwd_function "function";
  print_parameters  state param;
  print_token       state colon ":";
  print_type_expr   state ret_type;
  print_token       state kwd_is "is";
  print_expr        state return

and print_parameters state {value; _} =
  let {lpar; inside; rpar} = value in
  print_token state lpar "(";
  print_nsepseq state ";" print_param_decl inside;
  print_token state rpar ")"

and print_param_decl state = function
  ParamConst param_const -> print_param_const state param_const
| ParamVar   param_var   -> print_param_var   state param_var

and print_param_const state {value; _} =
  let {kwd_const; var; colon; param_type} = value in
  print_token     state kwd_const "const";
  print_var       state var;
  print_token     state colon ":";
  print_type_expr state param_type

and print_param_var state {value; _} =
  let {kwd_var; var; colon; param_type} = value in
  print_token     state kwd_var "var";
  print_var       state var;
  print_token     state colon ":";
  print_type_expr state param_type

and print_block state block =
  let {opening; statements; terminator; closing} = block.value in
  print_block_opening state opening;
  print_statements    state statements;
  print_terminator    state terminator;
  print_block_closing state closing

and print_block_opening state = function
  Block (kwd_block, lbrace) ->
    print_token state kwd_block "block";
    print_token state lbrace    "{"
| Begin kwd_begin ->
    print_token state kwd_begin "begin"

and print_block_closing state = function
  Block rbrace -> print_token state rbrace  "}"
| End kwd_end  -> print_token state kwd_end "end"

and print_data_decl state = function
  LocalConst decl -> print_const_decl state decl
| LocalVar   decl -> print_var_decl   state decl
| LocalFun  decl -> print_fun_decl  state decl

and print_var_decl state {value; _} =
  let {kwd_var; name; colon; var_type;
       assign; init; terminator} = value in
  print_token      state kwd_var "var";
  print_var        state name;
  print_token      state colon ":";
  print_type_expr  state var_type;
  print_token      state assign ":=";
  print_expr       state init;
  print_terminator state terminator

and print_statements state sequence =
  print_nsepseq state ";" print_statement sequence

and print_statement state = function
  Instr instr -> print_instruction state instr
| Data  data  -> print_data_decl   state data
| Attr  attr  -> print_attr_decl   state attr

and print_instruction state = function
  Cond        {value; _} -> print_conditional state value
| CaseInstr   {value; _} -> print_case_instr state value
| Assign      assign     -> print_assignment state assign
| Loop        loop       -> print_loop state loop
| ProcCall    fun_call   -> print_fun_call state fun_call
| Skip        kwd_skip   -> print_token state kwd_skip "skip"
| RecordPatch {value; _} -> print_record_patch state value
| MapPatch    {value; _} -> print_map_patch state value
| SetPatch    {value; _} -> print_set_patch state value
| MapRemove   {value; _} -> print_map_remove state value
| SetRemove   {value; _} -> print_set_remove state value

and print_cond_expr state (node: cond_expr) =
  print_token      state node.kwd_if "if";
  print_expr       state node.test;
  print_token      state node.kwd_then "then";
  print_expr       state node.ifso;
  print_terminator state node.terminator;
  print_token      state node.kwd_else "else";
  print_expr       state node.ifnot

and print_conditional state (node: conditional) =
  print_token      state node.kwd_if "if";
  print_expr       state node.test;
  print_token      state node.kwd_then "then";
  print_if_clause  state node.ifso;
  print_terminator state node.terminator;
  print_token      state node.kwd_else "else";
  print_if_clause  state node.ifnot

and print_if_clause state = function
  ClauseInstr instr -> print_instruction state instr
| ClauseBlock block -> print_clause_block state block

and print_clause_block state = function
  LongBlock block ->
    print_block state block
| ShortBlock {value; _} ->
    let {lbrace; inside; rbrace} = value in
    let statements, terminator = inside in
    print_token      state lbrace "{";
    print_statements state statements;
    print_terminator state terminator;
    print_token      state rbrace "}"

and print_case_instr state (node : if_clause case) =
  let {kwd_case; expr; opening;
       lead_vbar; cases; closing} = node in
  print_token       state kwd_case "case";
  print_expr        state expr;
  print_opening     state "of" opening;
  print_token_opt   state lead_vbar "|";
  print_cases_instr state cases;
  print_closing     state closing

and print_token_opt state = function
         None -> fun _ -> ()
| Some region -> print_token state region

and print_cases_instr state {value; _} =
  print_nsepseq state "|" print_case_clause_instr value

and print_case_clause_instr state {value; _} =
  let {pattern; arrow; rhs} = value in
  print_pattern   state pattern;
  print_token     state arrow "->";
  print_if_clause state rhs

and print_assignment state {value; _} =
  let {lhs; assign; rhs} = value in
  print_lhs   state lhs;
  print_token state assign ":=";
  print_rhs   state rhs

and print_rhs state e = print_expr state e

and print_lhs state = function
  Path path          -> print_path state path
| MapPath {value; _} -> print_map_lookup state value

and print_loop state = function
  While {value; _} -> print_while_loop state value
| For     for_loop -> print_for_loop   state for_loop

and print_while_loop state value =
  let {kwd_while; cond; block} = value in
  print_token state kwd_while "while";
  print_expr  state cond;
  print_block state block

and print_for_loop state = function
  ForInt     for_int     -> print_for_int     state for_int
| ForCollect for_collect -> print_for_collect state for_collect

and print_for_int state ({value; _} : for_int reg) =
  let {kwd_for; assign; kwd_to; bound; kwd_step; step; block} = value in
  print_token      state kwd_for "for";
  print_var_assign state assign;
  print_token      state kwd_to "to";
  print_expr       state bound;
  match kwd_step with 
  | None -> ();
  | Some kwd_step -> 
  print_token      state kwd_step "step";
  match step with
  | None -> ();
  | Some step ->
  print_expr       state step;
  print_block      state block

and print_var_assign state {value; _} =
  let {name; assign; expr} = value in
  print_var   state name;
  print_token state assign ":=";
  print_expr  state expr

and print_for_collect state ({value; _} : for_collect reg) =
  let {kwd_for; var; bind_to;
       kwd_in; collection; expr; block} = value in
  print_token      state kwd_for "for";
  print_var        state var;
  print_bind_to    state bind_to;
  print_token      state kwd_in "in";
  print_collection state collection;
  print_expr       state expr;
  print_block      state block

and print_collection state = function
  Map kwd_map ->
    print_token state kwd_map "map"
| Set kwd_set ->
    print_token state kwd_set "set"
| List kwd_list ->
    print_token state kwd_list "list"

and print_bind_to state = function
  Some (arrow, variable) ->
    print_token state arrow "->";
    print_var   state variable
| None -> ()

and print_expr state = function
  ECase   {value;_} -> print_case_expr state value
| ECond   {value;_} -> print_cond_expr state value
| EAnnot  {value;_} -> print_annot_expr state value
| ELogic  e -> print_logic_expr state e
| EArith  e -> print_arith_expr state e
| EString e -> print_string_expr state e
| EList   e -> print_list_expr state e
| ESet    e -> print_set_expr state e
| EConstr e -> print_constr_expr state e
| ERecord e -> print_record_expr state e
| EUpdate e -> print_update_expr state e
| EProj   e -> print_projection state e
| EMap    e -> print_map_expr state e
| EVar    v -> print_var state v
| ECall   e -> print_fun_call state e
| EBytes  b -> print_bytes state b
| EUnit   r -> print_token state r "Unit"
| ETuple  e -> print_tuple_expr state e
| EPar    e -> print_par_expr state e
| EFun    e -> print_fun_expr state e

and print_annot_expr state (expr , type_expr) =
  print_expr state expr;
  print_type_expr state type_expr

and print_case_expr state (node : expr case) =
  let {kwd_case; expr; opening;
       lead_vbar; cases; closing} = node in
  print_token      state kwd_case "case";
  print_expr       state expr;
  print_opening    state "of" opening;
  print_token_opt  state lead_vbar "|";
  print_cases_expr state cases;
  print_closing    state closing

and print_cases_expr state {value; _} =
  print_nsepseq state "|" print_case_clause_expr value

and print_case_clause_expr state {value; _} =
  let {pattern; arrow; rhs} = value in
  print_pattern state pattern;
  print_token   state arrow "->";
  print_expr    state rhs

and print_map_expr state = function
  MapLookUp {value; _} -> print_map_lookup state value
| MapInj inj           -> print_injection  state "map" print_binding inj
| BigMapInj inj        -> print_injection  state "big_map" print_binding inj

and print_set_expr state = function
  SetInj inj -> print_injection state "set" print_expr inj
| SetMem mem -> print_set_membership state mem

and print_set_membership state {value; _} =
  let {set; kwd_contains; element} = value in
  print_expr  state set;
  print_token state kwd_contains "contains";
  print_expr  state element

and print_map_lookup state {path; index} =
  let {lbracket; inside; rbracket} = index.value in
  print_path  state path;
  print_token state lbracket "[";
  print_expr  state inside;
  print_token state rbracket "]"

and print_path state = function
  Name var  -> print_var        state var
| Path path -> print_projection state path

and print_logic_expr state = function
  BoolExpr e -> print_bool_expr state e
| CompExpr e -> print_comp_expr state e

and print_bool_expr state = function
  Or {value = {arg1; op; arg2}; _} ->
    print_expr  state arg1;
    print_token state op "||";
    print_expr  state arg2
| And {value = {arg1; op; arg2}; _} ->
    print_expr  state arg1;
    print_token state op "&&";
    print_expr  state arg2
| Not {value = {op; arg}; _} ->
    print_token state op "not";
    print_expr  state arg
| False region ->
    print_token state region "False"
| True region ->
    print_token state region "True"

and print_comp_expr state = function
  Lt {value = {arg1; op; arg2}; _} ->
    print_expr  state arg1;
    print_token state op "<";
    print_expr  state arg2
| Leq {value = {arg1; op; arg2}; _} ->
    print_expr  state arg1;
    print_token state op "<=";
    print_expr  state arg2
| Gt {value = {arg1; op; arg2}; _} ->
    print_expr  state arg1;
    print_token state op ">";
    print_expr  state arg2
| Geq {value = {arg1; op; arg2}; _} ->
    print_expr  state arg1;
    print_token state op ">=";
    print_expr  state arg2
| Equal {value = {arg1; op; arg2}; _} ->
    print_expr  state arg1;
    print_token state op "=";
    print_expr  state arg2
| Neq {value = {arg1; op; arg2}; _} ->
    print_expr  state arg1;
    print_token state op "=/=";
    print_expr  state arg2

and print_arith_expr state = function
  Add {value = {arg1; op; arg2}; _} ->
    print_expr  state arg1;
    print_token state op "+";
    print_expr  state arg2
| Sub {value = {arg1; op; arg2}; _} ->
    print_expr  state arg1;
    print_token state op "-";
    print_expr  state arg2
| Mult {value = {arg1; op; arg2}; _} ->
    print_expr  state arg1;
    print_token state op "*";
    print_expr  state arg2
| Div {value = {arg1; op; arg2}; _} ->
    print_expr  state arg1;
    print_token state op "/";
    print_expr  state arg2
| Mod {value = {arg1; op; arg2}; _} ->
    print_expr  state arg1;
    print_token state op "mod";
    print_expr  state arg2
| Neg {value = {op; arg}; _} ->
    print_token state op "-";
    print_expr  state arg
| Int i
| Nat i
| Mutez i -> print_int state i

and print_string_expr state = function
  Cat {value = {arg1; op; arg2}; _} ->
    print_expr  state arg1;
    print_token state op "^";
    print_expr  state arg2
| String s ->
    print_string state s
| Verbatim v ->
    print_string state v

and print_list_expr state = function
  ECons {value = {arg1; op; arg2}; _} ->
    print_expr  state arg1;
    print_token state op "#";
    print_expr  state arg2
| EListComp e -> print_injection state "list" print_expr e
| ENil e -> print_nil state e

and print_constr_expr state = function
  SomeApp e   -> print_some_app   state e
| NoneExpr e  -> print_none_expr  state e
| ConstrApp e -> print_constr_app state e

and print_record_expr state e =
  print_ne_injection state "record" print_field_assign e

and print_field_assign state {value; _} =
  let {field_name; equal; field_expr} = value in
  print_var   state field_name;
  print_token state equal "=";
  print_expr  state field_expr

and print_field_path_assign state {value; _} =
  let {field_path; equal; field_expr} = value in
  print_nsepseq state "field_path" print_var field_path;
  print_token state equal "=";
  print_expr  state field_expr

and print_update_expr state {value; _} =
  let {record; kwd_with; updates} = value in
  print_path state record;
  print_token state kwd_with "with";
  print_ne_injection state "updates field" print_field_path_assign updates


and print_projection state {value; _} =
  let {struct_name; selector; field_path} = value in
  print_var        state struct_name;
  print_token      state selector ".";
  print_field_path state field_path

and print_field_path state sequence =
  print_nsepseq state "." print_selection sequence

and print_selection state = function
  FieldName name -> print_var state name
| Component int  -> print_int state int

and print_record_patch state node =
  let {kwd_patch; path; kwd_with; record_inj} = node in
  print_token        state kwd_patch "patch";
  print_path         state path;
  print_token        state kwd_with "with";
  print_ne_injection state "record" print_field_assign record_inj

and print_set_patch state node =
  let {kwd_patch; path; kwd_with; set_inj} = node in
  print_token        state kwd_patch "patch";
  print_path         state path;
  print_token        state kwd_with "with";
  print_ne_injection state "set" print_expr set_inj

and print_map_patch state node =
  let {kwd_patch; path; kwd_with; map_inj} = node in
  print_token        state kwd_patch "patch";
  print_path         state path;
  print_token        state kwd_with "with";
  print_ne_injection state "map" print_binding map_inj

and print_map_remove state node =
  let {kwd_remove; key; kwd_from; kwd_map; map} = node in
  print_token state kwd_remove "remove";
  print_expr  state key;
  print_token state kwd_from "from";
  print_token state kwd_map "map";
  print_path  state map

and print_set_remove state node =
  let {kwd_remove; element; kwd_from; kwd_set; set} = node in
  print_token state kwd_remove "remove";
  print_expr  state element;
  print_token state kwd_from "from";
  print_token state kwd_set "set";
  print_path  state set

and print_injection :
  'a.state -> string -> (state -> 'a -> unit) ->
  'a injection reg -> unit =
  fun state kwd print {value; _} ->
    let {opening; elements; terminator; closing} = value in
    print_opening    state kwd opening;
    print_sepseq     state ";" print elements;
    print_terminator state terminator;
    print_closing    state closing

and print_ne_injection :
  'a.state -> string -> (state -> 'a -> unit) ->
  'a ne_injection reg -> unit =
  fun state kwd print {value; _} ->
    let {opening; ne_elements; terminator; closing} = value in
    print_opening    state kwd opening;
    print_nsepseq    state ";" print ne_elements;
    print_terminator state terminator;
    print_closing    state closing

and print_opening state lexeme = function
  Kwd kwd ->
    print_token state kwd lexeme
| KwdBracket (kwd, lbracket) ->
    print_token state kwd lexeme;
    print_token state lbracket "["

and print_closing state = function
  RBracket rbracket -> print_token state rbracket "]"
| End kwd_end       -> print_token state kwd_end "end"

and print_binding state {value; _} =
  let {source; arrow; image} = value in
  print_expr  state source;
  print_token state arrow "->";
  print_expr  state image

and print_tuple_expr state {value; _} =
  let {lpar; inside; rpar} = value in
  print_token   state lpar "(";
  print_nsepseq state "," print_expr inside;
  print_token   state rpar ")"

and print_nil state value = print_token state value "nil"

and print_none_expr state value = print_token state value "None"

and print_fun_call state {value; _} =
  let expr, arguments = value in
  print_expr       state expr;
  print_tuple_expr state arguments

and print_constr_app state {value; _} =
  let constr, arguments = value in
  print_constr state constr;
  match arguments with
    None -> ()
  | Some arg -> print_tuple_expr state arg

and print_some_app state {value; _} =
  let c_Some, arguments = value in
  print_token      state c_Some "Some";
  print_tuple_expr state arguments

and print_par_expr state {value; _} =
  let {lpar; inside; rpar} = value in
  print_token state lpar "(";
  print_expr  state inside;
  print_token state rpar ")"

and print_pattern state = function
  PVar var         -> print_var state var
| PWild wild       -> print_token state wild "_"
| PInt i           -> print_int state i
| PNat n           -> print_nat state n
| PBytes b         -> print_bytes state b
| PString s        -> print_string state s
| PList pattern    -> print_list_pattern state pattern
| PTuple ptuple    -> print_ptuple state ptuple
| PConstr pattern  -> print_constr_pattern state pattern

and print_constr_pattern state = function
  PUnit region     -> print_token state region "Unit"
| PFalse region    -> print_token state region "False"
| PTrue region     -> print_token state region "True"
| PNone region     -> print_token state region "None"
| PSomeApp psome   -> print_psome state psome
| PConstrApp {value; _} ->
    let constr, arg = value in
    print_constr state constr;
    match arg with
      None -> ()
    | Some tuple -> print_ptuple state tuple

and print_psome state {value; _} =
  let c_Some, patterns = value in
  print_token    state c_Some "Some";
  print_patterns state patterns

and print_patterns state {value; _} =
  let {lpar; inside; rpar} = value in
  print_token   state lpar "(";
  print_pattern state inside;
  print_token   state rpar ")"

and print_list_pattern state = function
  PListComp comp ->
   print_injection state "list" print_pattern comp
| PNil kwd_nil ->
    print_token state kwd_nil "nil"
| PParCons cons ->
    print_par_cons state cons
| PCons {value; _} ->
   print_nsepseq state "#" print_pattern value

and print_par_cons state {value; _} =
  let {lpar; inside; rpar} = value in
  let head, cons, tail = inside in
  print_token   state lpar "(";
  print_pattern state head;
  print_token   state cons "#";
  print_pattern state tail;
  print_token   state rpar ")"

and print_ptuple state {value; _} =
  let {lpar; inside; rpar} = value in
  print_token   state lpar "(";
  print_nsepseq state "," print_pattern inside;
  print_token   state rpar ")"

and print_terminator state = function
  Some semi -> print_token state semi ";"
| None -> ()

(* Conversion to string *)

let to_string ~offsets ~mode printer node =
  let buffer = Buffer.create 131 in
  let state = mk_state ~offsets ~mode ~buffer in
  let () = printer state node
  in Buffer.contents buffer

let tokens_to_string ~offsets ~mode =
  to_string ~offsets ~mode print_tokens
let path_to_string ~offsets ~mode =
  to_string ~offsets ~mode print_path
let pattern_to_string ~offsets ~mode =
  to_string ~offsets ~mode print_pattern
let instruction_to_string ~offsets ~mode =
  to_string ~offsets ~mode print_instruction

(** {1 Pretty-printing the AST} *)

let pp_ident state {value=name; region} =
  let reg  = compact state region in
  let node = sprintf "%s%s (%s)\n" state#pad_path name reg
  in Buffer.add_string state#buffer node

let pp_node state name =
  let node = sprintf "%s%s\n" state#pad_path name
  in Buffer.add_string state#buffer node

let pp_string state = pp_ident state

let pp_loc_node state name region =
  pp_ident state {value=name; region}

let rec pp_ast state {decl; _} =
  let apply len rank =
    pp_declaration (state#pad len rank) in
  let decls = Utils.nseq_to_list decl in
  pp_node state "<ast>";
  List.iteri (List.length decls |> apply) decls

and pp_declaration state = function
  TypeDecl {value; region} ->
    pp_loc_node state "TypeDecl" region;
    pp_ident (state#pad 2 0) value.name;
    pp_type_expr (state#pad 2 1) value.type_expr
| ConstDecl {value; region} ->
    pp_loc_node state "ConstDecl" region;
    pp_const_decl state value
| FunDecl {value; region} ->
    pp_loc_node state "FunDecl" region;
    pp_fun_decl state value
| AttrDecl {value; region} ->
    pp_loc_node state "AttrDecl" region;
    pp_attr_decl state value

and pp_attr_decl state = pp_ne_injection pp_string state

and pp_fun_decl state decl =
  let arity, start =
    match decl.kwd_recursive with
      None -> 5,0
    | Some _ ->
       let state = state#pad 6 0 in
       let () = pp_node state "recursive"
       in 6,1 in
  let () =
    let state = state#pad arity start in
    pp_ident state decl.fun_name in
  let () =
    let state = state#pad arity (start + 1) in
    pp_node state "<parameters>";
    pp_parameters state decl.param in
  let () =
    let state = state#pad arity (start + 2) in
    pp_node state "<return type>";
    pp_type_expr (state#pad 1 0) decl.ret_type in
  let () =
    let state = state#pad arity (start + 3) in
    pp_node state "<body>";
    let statements =
      match decl.block_with with
        Some (block,_) -> block.value.statements
      | None -> Instr (Skip Region.ghost), [] in
    pp_statements state statements in
  let () =
    let state = state#pad arity (start + 4) in
    pp_node state "<return>";
    pp_expr (state#pad 1 0) decl.return
  in ()

and pp_const_decl state decl =
  let arity = 3 in
  pp_ident (state#pad arity 0) decl.name;
  pp_type_expr (state#pad arity 1) decl.const_type;
  pp_expr (state#pad arity 2) decl.init

and pp_type_expr state = function
  TProd cartesian ->
    pp_loc_node state "TProd" cartesian.region;
    pp_cartesian state cartesian
| TVar v ->
    pp_node state "TVar";
    pp_ident (state#pad 1 0) v
| TPar {value; region} ->
    pp_loc_node state "TPar" region;
    pp_type_expr (state#pad 1 0) value.inside
| TApp {value=name,tuple; region} ->
    pp_loc_node state "TApp" region;
    pp_ident (state#pad 1 0) name;
    pp_type_tuple (state#pad 2 1) tuple
| TFun {value; region} ->
    pp_loc_node state "TFun" region;
    let apply len rank =
      pp_type_expr (state#pad len rank) in
    let domain, _, range = value in
    List.iteri (apply 2) [domain; range]
| TSum {value; region} ->
    pp_loc_node state "TSum" region;
    let apply len rank variant =
      pp_variant (state#pad len rank) variant.value in
    let variants = Utils.nsepseq_to_list value in
    List.iteri (List.length variants |> apply) variants
| TRecord {value; region} ->
    pp_loc_node state "TRecord" region;
    let apply len rank field_decl =
      pp_field_decl (state#pad len rank)
                    field_decl.value in
    let fields = Utils.nsepseq_to_list value.ne_elements in
    List.iteri (List.length fields |> apply) fields
| TStringLiteral s ->
  pp_node   state "String";
  pp_string (state#pad 1 0) s

and pp_cartesian state {value; _} =
  let apply len rank =
    pp_type_expr (state#pad len rank) in
  let components = Utils.nsepseq_to_list value
  in List.iteri (List.length components |> apply) components

and pp_variant state {constr; arg} =
  pp_ident state constr;
  match arg with
          None -> ()
  | Some (_,c) -> pp_type_expr (state#pad 1 0) c

and pp_field_decl state decl =
  pp_ident state decl.field_name;
  pp_type_expr (state#pad 1 0) decl.field_type

and pp_type_tuple state {value; _} =
  let components = Utils.nsepseq_to_list value.inside in
  let apply len rank = pp_type_expr (state#pad len rank)
  in List.iteri (List.length components |> apply) components

and pp_fun_expr state (expr: fun_expr) =
  let () =
    let state = state#pad 3 0 in
    pp_node state "<parameters>";
    pp_parameters state expr.param in
  let () =
    let state = state#pad 3 1 in
    pp_node state "<return type>";
    pp_type_expr (state#pad 1 0) expr.ret_type in
  let () =
    let state = state#pad 3 2 in
    pp_node state "<return>";
    pp_expr (state#pad 1 0) expr.return
  in ()

and pp_parameters state {value; _} =
  let params = Utils.nsepseq_to_list value.inside in
  let arity  = List.length params in
  let apply len rank = pp_param_decl (state#pad len rank)
  in List.iteri (apply arity) params

and pp_param_decl state = function
  ParamConst {value; region} ->
    pp_loc_node state "ParamConst" region;
    pp_ident (state#pad 2 0) value.var;
    pp_type_expr (state#pad 2 1) value.param_type
| ParamVar {value; region} ->
    pp_loc_node state "ParamVar" region;
    pp_ident (state#pad 2 0) value.var;
    pp_type_expr (state#pad 2 1) value.param_type

and pp_statements state statements =
  let statements = Utils.nsepseq_to_list statements in
  let length     = List.length statements in
  let apply len rank = pp_statement (state#pad len rank)
  in List.iteri (apply length) statements

and pp_statement state = function
  Instr instr ->
    pp_node state "Instr";
    pp_instruction (state#pad 1 0) instr
| Data data_decl ->
    pp_node state "Data";
    pp_data_decl (state#pad 1 0) data_decl
| Attr attr_decl ->
    pp_node state "Attr";
    pp_attr_decl state attr_decl.value

and pp_instruction state = function
  Cond {value; region} ->
    pp_loc_node state "Cond" region;
    pp_conditional state value
| CaseInstr {value; region} ->
    pp_loc_node state "CaseInstr" region;
    pp_case pp_if_clause state value
| Assign {value; region} ->
    pp_loc_node state "Assign" region;
    pp_assignment state value
| Loop loop ->
    pp_node state "Loop";
    pp_loop (state#pad 1 0) loop
| ProcCall {value; region} ->
    pp_loc_node state "ProcCall" region;
    pp_fun_call state value
| Skip region ->
    pp_loc_node state "Skip" region
| RecordPatch {value; region} ->
    pp_loc_node state "RecordPatch" region;
    pp_record_patch state value
| MapPatch {value; region} ->
    pp_loc_node state "MapPatch" region;
    pp_map_patch state value
| SetPatch {value; region} ->
    pp_loc_node state "SetPatch" region;
    pp_set_patch state value
| MapRemove {value; region} ->
    pp_loc_node state "MapRemove" region;
    pp_map_remove state value
| SetRemove {value; region} ->
    pp_loc_node state "SetRemove" region;
    pp_set_remove state value

and pp_cond_expr state (cond: cond_expr) =
  let () =
    let state = state#pad 3 0 in
    pp_node state "<condition>";
    pp_expr (state#pad 1 0) cond.test in
  let () =
    let state = state#pad 3 1 in
    pp_node state "<true>";
    pp_expr (state#pad 1 0) cond.ifso in
  let () =
    let state = state#pad 3 2 in
    pp_node state "<false>";
    pp_expr (state#pad 1 0) cond.ifnot
  in ()

and pp_conditional state (cond: conditional) =
  let () =
    let state = state#pad 3 0 in
    pp_node state "<condition>";
    pp_expr (state#pad 1 0) cond.test in
  let () =
    let state = state#pad 3 1 in
    pp_node state "<true>";
    pp_if_clause (state#pad 1 0) cond.ifso in
  let () =
    let state = state#pad 3 2 in
    pp_node state "<false>";
    pp_if_clause (state#pad 1 0) cond.ifnot
  in ()

and pp_if_clause state = function
  ClauseInstr instr ->
    pp_node state "ClauseInstr";
    pp_instruction (state#pad 1 0) instr
| ClauseBlock block ->
    pp_node state "ClauseBlock";
    pp_clause_block (state#pad 1 0) block

and pp_clause_block state = function
  LongBlock {value; region} ->
    pp_loc_node state "LongBlock" region;
    pp_statements state value.statements
| ShortBlock {value; region} ->
    pp_loc_node state "ShortBlock" region;
    pp_statements state (fst value.inside)

and pp_case :
  'a.(state -> 'a -> unit) -> state -> 'a case -> unit =
  fun printer state case ->
    let clauses = Utils.nsepseq_to_list case.cases.value in
    let clauses = List.map (fun {value; _} -> value) clauses in
    let length  = List.length clauses + 1 in
    let apply len rank =
      let state = state#pad len (rank+1)
      in pp_case_clause printer state
    in pp_expr (state#pad length 0) case.expr;
    List.iteri (apply length) clauses

and pp_case_clause :
  'a.(state -> 'a -> unit) -> state -> 'a case_clause -> unit =
  fun printer state clause ->
    pp_node state "<clause>";
    pp_pattern (state#pad 2 0) clause.pattern;
    printer (state#pad 2 1) clause.rhs

and pp_pattern state = function
  PWild region ->
    pp_loc_node state "PWild" region
| PConstr pattern ->
    pp_node state "PConstr";
    pp_constr_pattern (state#pad 1 0) pattern
| PVar v ->
    pp_node state "PVar";
    pp_ident (state#pad 1 0) v
| PInt n ->
    pp_node state "PInt";
    pp_int state n
| PNat n ->
    pp_node state "PNat";
    pp_int state n
| PBytes b ->
    pp_node state "PBytes";
    pp_bytes state b
| PString s ->
    pp_node state "PString";
    pp_ident (state#pad 1 0) s
| PList plist ->
    pp_node state "PList";
    pp_list_pattern (state#pad 1 0) plist
| PTuple {value; region} ->
    pp_loc_node state "PTuple" region;
    pp_tuple_pattern (state#pad 1 0) value

and pp_bytes state {value=lexeme,hex; region} =
  pp_loc_node (state#pad 2 0) lexeme region;
  pp_node     (state#pad 2 1) (Hex.show hex)

and pp_int state {value=lexeme,z; region} =
  pp_loc_node (state#pad 2 0) lexeme region;
  pp_node     (state#pad 2 1) (Z.to_string z)

and pp_constr_pattern state = function
  PNone region ->
    pp_loc_node state "PNone" region
| PSomeApp {value=_,{value=par; _}; region} ->
    pp_loc_node state "PSomeApp" region;
    pp_pattern (state#pad 1 0) par.inside
| PUnit region ->
    pp_loc_node state "PUnit" region
| PFalse region ->
    pp_loc_node state "PFalse" region
| PTrue region ->
    pp_loc_node state "PTrue" region
| PConstrApp {value; region} ->
    pp_loc_node state "PConstrApp" region;
    pp_constr_app_pattern (state#pad 1 0) value

and pp_constr_app_pattern state (constr, pat_opt) =
  pp_ident state constr;
  match pat_opt with
               None -> ()
  | Some {value; _} -> pp_tuple_pattern state value

and pp_list_pattern state = function
  PListComp {value; region} ->
    pp_loc_node state "PListComp" region;
    pp_injection pp_pattern (state#pad 1 0) value
| PNil region ->
    pp_loc_node state "PNil" region
| PParCons {value; region} ->
    pp_loc_node state "PParCons" region;
    pp_bin_cons (state#pad 1 0) value.inside
| PCons {value; region} ->
    let patterns = Utils.nsepseq_to_list value in
    let length   = List.length patterns in
    let apply len rank =
      pp_pattern (state#pad len rank) in
    pp_loc_node state "PCons" region;
    List.iteri (apply length) patterns

and pp_bin_cons state (head, _, tail) =
  pp_pattern (state#pad 2 0) head;
  pp_pattern (state#pad 2 1) tail

and pp_injection :
  'a.(state -> 'a -> unit) -> state -> 'a injection -> unit =
  fun printer state inj ->
    let elements       = Utils.sepseq_to_list inj.elements in
    let length         = List.length elements in
    let apply len rank = printer (state#pad len rank)
    in List.iteri (apply length) elements

and pp_ne_injection :
  'a.(state -> 'a -> unit) -> state -> 'a ne_injection -> unit =
  fun printer state inj ->
    let ne_elements    = Utils.nsepseq_to_list inj.ne_elements in
    let length         = List.length ne_elements in
    let apply len rank = printer (state#pad len rank)
    in List.iteri (apply length) ne_elements

and pp_tuple_pattern state tuple =
  let patterns       = Utils.nsepseq_to_list tuple.inside in
  let length         = List.length patterns in
  let apply len rank = pp_pattern (state#pad len rank)
  in List.iteri (apply length) patterns

and pp_assignment state asgn =
  pp_lhs  (state#pad 2 0) asgn.lhs;
  pp_expr (state#pad 2 1) asgn.rhs

and pp_lhs state = function
  Path path ->
    pp_node state "Path";
    pp_path (state#pad 1 0) path
| MapPath {value; region} ->
    pp_loc_node state "MapPath" region;
    pp_map_lookup state value

and pp_path state = function
  Name name ->
    pp_node state "Name";
    pp_ident (state#pad 1 0) name
| Path {value; region} ->
    pp_loc_node state "Path" region;
    pp_projection state value

and pp_projection state proj =
  let selections     = Utils.nsepseq_to_list proj.field_path in
  let len            = List.length selections in
  let apply len rank = pp_selection (state#pad len rank) in
  pp_ident (state#pad (1+len) 0) proj.struct_name;
  List.iteri (apply len) selections

and pp_update state update =
  pp_path state update.record;
  pp_ne_injection pp_field_path_assign state update.updates.value

and pp_selection state = function
  FieldName name ->
    pp_node state "FieldName";
    pp_ident (state#pad 1 0) name
| Component comp ->
    pp_node state "Component";
    pp_int state comp

and pp_map_lookup state lookup =
  pp_path (state#pad 2 0) lookup.path;
  pp_expr (state#pad 2 1) lookup.index.value.inside

and pp_loop state = function
  While {value; _} ->
    pp_node state "<while>";
    let () =
      let state = state#pad 2 0 in
      pp_node state "<condition>";
      pp_expr (state#pad 1 0) value.cond in
    let () =
      let state = state#pad 2 1 in
      let statements = value.block.value.statements in
      pp_node state "<statements>";
      pp_statements state statements
    in ()
| For for_loop ->
    pp_node state "<for>";
    pp_for_loop (state#pad 1 0) for_loop

and pp_for_loop state = function
  ForInt {value; region} ->
    pp_loc_node state "ForInt" region;
    pp_for_int state value
| ForCollect {value; region} ->
    pp_loc_node state "ForCollect" region;
    pp_for_collect state value

and pp_for_int state for_int =
  let () =
    let state = state#pad 3 0 in
    pp_node state "<init>";
    pp_var_assign state for_int.assign.value in
  let () =
    let state = state#pad 3 1 in
    pp_node state "<bound>";
    pp_expr (state#pad 1 0) for_int.bound in
  let () =
    let state = state#pad 3 2 in
    let statements = for_int.block.value.statements in
    pp_node state "<statements>";
    pp_statements state statements
  in ()

and pp_var_assign state asgn =
  pp_ident (state#pad 2 0) asgn.name;
  pp_expr  (state#pad 2 1) asgn.expr

and pp_for_collect state collect =
  let () =
    let state = state#pad 3 0 in
    match collect.bind_to with
      None ->
        pp_ident state collect.var
    | Some (_, var) ->
        pp_var_binding state (collect.var, var) in
  let () =
    let state = state#pad 3 1 in
    pp_node state "<collection>";
    pp_collection (state#pad 2 0) collect.collection;
    pp_expr (state#pad 1 0) collect.expr in
  let () =
      let state = state#pad 3 2 in
      let statements = collect.block.value.statements in
      pp_node state "<statements>";
      pp_statements state statements
  in ()

and pp_collection state = function
  Map  region -> pp_loc_node state "map"  region
| Set  region -> pp_loc_node state "set"  region
| List region -> pp_loc_node state "list" region

and pp_var_binding state (source, image) =
  pp_node  state "<binding>";
  pp_ident (state#pad 2 0) source;
  pp_ident (state#pad 2 1) image

and pp_fun_call state (expr, args) =
  let args           = Utils.nsepseq_to_list args.value.inside in
  let arity          = List.length args in
  let apply len rank = pp_expr (state#pad len rank)
  in pp_expr (state#pad (1+arity) 0) expr;
     List.iteri (apply arity) args

and pp_record_patch state patch =
  pp_path (state#pad 2 0) patch.path;
  pp_ne_injection pp_field_assign state patch.record_inj.value

and pp_field_assign state {value; _} =
  pp_node  state "<field assignment>";
  pp_ident (state#pad 2 0) value.field_name;
  pp_expr  (state#pad 2 1) value.field_expr

and pp_field_path_assign state {value; _} =
  pp_node  state "<field path for update>";
  let path = Utils.nsepseq_to_list value.field_path in
  List.iter (pp_ident (state#pad 2 0)) path;
  pp_expr  (state#pad 2 1) value.field_expr

and pp_map_patch state patch =
  pp_path (state#pad 2 0) patch.path;
  pp_ne_injection pp_binding state patch.map_inj.value

and pp_binding state {value; _} =
  let source, image = value.source, value.image in
  pp_node state "<binding>";
  pp_expr (state#pad 2 0) source;
  pp_expr (state#pad 2 1) image

and pp_set_patch state patch =
  pp_path (state#pad 2 0) patch.path;
  pp_ne_injection pp_expr state patch.set_inj.value

and pp_map_remove state rem =
  pp_expr (state#pad 2 0) rem.key;
  pp_path (state#pad 2 1) rem.map

and pp_set_remove state rem =
  pp_expr (state#pad 2 0) rem.element;
  pp_path (state#pad 2 1) rem.set

and pp_data_decl state = function
  LocalConst {value; region} ->
    pp_loc_node state "LocalConst" region;
    pp_const_decl state value
| LocalVar {value; region} ->
    pp_loc_node state "LocalVar" region;
    pp_var_decl state value
| LocalFun {value; region} ->
    pp_loc_node state "LocalFun" region;
    pp_fun_decl state value

and pp_var_decl state decl =
  pp_ident     (state#pad 3 0) decl.name;
  pp_type_expr (state#pad 3 1) decl.var_type;
  pp_expr      (state#pad 3 2) decl.init

and pp_expr state = function
  ECase {value; region} ->
    pp_loc_node state "ECase" region;
    pp_case pp_expr state value
| ECond {value; region} ->
    pp_loc_node state "ECond" region;
    pp_cond_expr state value
| EAnnot {value; region} ->
    pp_loc_node state "EAnnot" region;
    pp_annotated state value
| ELogic e_logic ->
    pp_node state "ELogic";
    pp_e_logic (state#pad 1 0) e_logic
| EArith e_arith ->
    pp_node state "EArith";
    pp_arith_expr (state#pad 1 0) e_arith
| EString e_string ->
    pp_node state "EString";
    pp_string_expr (state#pad 1 0) e_string
| EList e_list ->
    pp_node state "EList";
    pp_list_expr (state#pad 1 0) e_list
| ESet e_set ->
    pp_node state "ESet";
    pp_set_expr (state#pad 1 0) e_set
| EConstr e_constr ->
    pp_node state "EConstr";
    pp_constr_expr (state#pad 1 0) e_constr
| ERecord {value; region} ->
    pp_loc_node state "ERecord" region;
    pp_ne_injection pp_field_assign state value
| EProj {value; region} ->
    pp_loc_node state "EProj" region;
    pp_projection state value
| EUpdate {value; region} ->
    pp_loc_node state "EUpdate" region;
    pp_update state value
| EMap e_map ->
    pp_node state "EMap";
    pp_map_expr (state#pad 1 0) e_map
| EVar v ->
    pp_node state "EVar";
    pp_ident (state#pad 1 0) v
| ECall {value; region} ->
    pp_loc_node state "ECall" region;
    pp_fun_call state value
| EBytes b ->
    pp_node state "EBytes";
    pp_bytes state b
| EUnit region ->
    pp_loc_node state "EUnit" region
| ETuple e_tuple ->
    pp_node state "ETuple";
    pp_tuple_expr state e_tuple
| EPar {value; region} ->
    pp_loc_node state "EPar" region;
    pp_expr (state#pad 1 0) value.inside
| EFun {value; region} ->
    pp_loc_node state "EFun" region;
    pp_fun_expr state value;

and pp_list_expr state = function
  ECons {value; region} ->
    pp_loc_node state "ECons" region;
    pp_expr (state#pad 2 0) value.arg1;
    pp_expr (state#pad 2 1) value.arg2
| ENil region ->
    pp_loc_node state "ENil" region
| EListComp {value; region} ->
    pp_loc_node state "EListComp" region;
    if value.elements = None then
      pp_node (state#pad 1 0) "[]"
    else pp_injection pp_expr state value

and pp_arith_expr state = function
  Add {value; region} ->
    pp_bin_op "Add" region state value
| Sub {value; region} ->
    pp_bin_op "Sub" region state value
| Mult {value; region} ->
    pp_bin_op "Mult" region state value
| Div {value; region} ->
    pp_bin_op "Div" region state value
| Mod {value; region} ->
    pp_bin_op "Mod" region state value
| Neg {value; region} ->
    pp_loc_node state "Neg" region;
    pp_expr (state#pad 1 0) value.arg;
| Int i ->
    pp_node state "Int";
    pp_int  state i
| Nat n ->
    pp_node state "Nat";
    pp_int  state n
| Mutez m ->
    pp_node state "Mutez";
    pp_int  state m

and pp_set_expr state = function
  SetInj {value; region} ->
    pp_loc_node  state "SetInj" region;
    pp_injection pp_expr state value
| SetMem {value; region} ->
    pp_loc_node state "SetMem" region;
    pp_expr     (state#pad 2 0) value.set;
    pp_expr     (state#pad 2 1) value.element

and pp_e_logic state = function
  BoolExpr e ->
    pp_node state "BoolExpr";
    pp_bool_expr (state#pad 1 0) e
| CompExpr e ->
    pp_node state "CompExpr";
    pp_comp_expr (state#pad 1 0) e

and pp_bool_expr state = function
  Or {value; region} ->
    pp_bin_op "Or" region state value
| And {value; region} ->
    pp_bin_op "And" region state value
| Not {value; region} ->
    pp_loc_node state "Not" region;
    pp_expr     (state#pad 1 0) value.arg
| False region ->
    pp_loc_node state "False" region
| True region ->
    pp_loc_node state "True" region

and pp_comp_expr state = function
  Lt {value; region} ->
    pp_bin_op "Lt" region state value
| Leq {value; region} ->
    pp_bin_op "Leq" region state value
| Gt {value; region} ->
    pp_bin_op "Gt" region state value
| Geq {value; region} ->
    pp_bin_op "Geq" region state value
| Equal {value; region} ->
    pp_bin_op "Equal" region state  value
| Neq {value; region} ->
    pp_bin_op "Neq" region state value

and pp_constr_expr state = function
  NoneExpr region ->
    pp_loc_node state "NoneExpr" region
| SomeApp {value=_,args; region} ->
    pp_loc_node state "SomeApp" region;
    pp_tuple_expr state args
| ConstrApp {value; region} ->
    pp_loc_node state "ConstrApp" region;
    pp_constr_app (state#pad 1 0) value

and pp_constr_app state (constr, args_opt) =
  pp_ident state constr;
  match args_opt with
         None -> ()
  | Some args -> pp_tuple_expr state args

and pp_map_expr state = function
  MapLookUp {value; region} ->
    pp_loc_node   state "MapLookUp" region;
    pp_map_lookup state value
| MapInj {value; region} | BigMapInj {value; region} ->
    pp_loc_node  state "MapInj" region;
    pp_injection pp_binding state value

and pp_tuple_expr state {value; _} =
  let exprs          = Utils.nsepseq_to_list value.inside in
  let length         = List.length exprs in
  let apply len rank = pp_expr (state#pad len rank)
  in List.iteri (apply length) exprs

and pp_string_expr state = function
  Cat {value; region} ->
    pp_loc_node state "Cat" region;
    pp_expr     (state#pad 2 0) value.arg1;
    pp_expr     (state#pad 2 1) value.arg2;
| String s ->
    pp_node   state "String";
    pp_string (state#pad 1 0) s
| Verbatim v ->
    pp_node   state "Verbatim";
    pp_string (state#pad 1 0) v

and pp_annotated state (expr, t_expr) =
  pp_expr      (state#pad 2 0) expr;
  pp_type_expr (state#pad 2 1) t_expr

and pp_bin_op node region state op =
  pp_loc_node state node region;
  pp_expr     (state#pad 2 0) op.arg1;
  pp_expr     (state#pad 2 1) op.arg2
