module T = struct
  type 'a map = 'a -> 'a
  type 'a iter = 'a -> unit
  type ('a, 'acc) fold = 'a -> 'acc -> 'acc
  type ('a, 'acc) fold_map = 'a -> 'acc -> 'a * 'acc
  type ('ctx, 'a) map_with_context = 'ctx -> 'a -> 'a
  type ('a, 'res) lift = 'a -> 'res
  type ('ctx, 'a, 'res) lift_map_with_context = 'ctx -> 'a -> 'a * 'res
end

class map =
  let any x = x in
  object
    method int : int T.map = any
    method string : string T.map = any
    method bool : bool T.map = any
    method char : char T.map = any

    method option : 'a. 'a T.map -> 'a option T.map =
      fun f x -> match x with None -> None | Some x -> Some (f x)

    method list : 'a. 'a T.map -> 'a list T.map = List.map
    method array : 'a. 'a T.map -> 'a array T.map = Array.map
  end

class iter =
  let any = ignore in
  object
    method int : int T.iter = any
    method string : string T.iter = any
    method bool : bool T.iter = any
    method char : char T.iter = any

    method option : 'a. 'a T.iter -> 'a option T.iter =
      fun f x -> match x with None -> () | Some x -> f x

    method list : 'a. 'a T.iter -> 'a list T.iter = List.iter
    method array : 'a. 'a T.iter -> 'a array T.iter = Array.iter
  end

class ['acc] fold =
  let any _ acc = acc in
  object
    method int : (int, 'acc) T.fold = any
    method string : (string, 'acc) T.fold = any
    method bool : (bool, 'acc) T.fold = any
    method char : (char, 'acc) T.fold = any

    method option : 'a. ('a, 'acc) T.fold -> ('a option, 'acc) T.fold =
      fun f x acc -> match x with None -> acc | Some x -> f x acc

    method list : 'a. ('a, 'acc) T.fold -> ('a list, 'acc) T.fold =
      let rec loop f l acc =
        match l with [] -> acc | x :: l -> loop f l (f x acc)
      in
      loop

    method array : 'a. ('a, 'acc) T.fold -> ('a array, 'acc) T.fold =
      fun f a acc ->
        let r = ref acc in
        for i = 0 to Array.length a - 1 do
          r := f (Array.unsafe_get a i) !r
        done;
        !r
  end

class ['acc] fold_map =
  let any x acc = (x, acc) in
  object
    method int : (int, 'acc) T.fold_map = any
    method string : (string, 'acc) T.fold_map = any
    method bool : (bool, 'acc) T.fold_map = any
    method char : (char, 'acc) T.fold_map = any

    method option : 'a. ('a, 'acc) T.fold_map -> ('a option, 'acc) T.fold_map =
      fun f x acc ->
        match x with
        | None -> (None, acc)
        | Some x ->
            let x, acc = f x acc in
            (Some x, acc)

    method list : 'a. ('a, 'acc) T.fold_map -> ('a list, 'acc) T.fold_map =
      let rec loop f l acc =
        match l with
        | [] -> ([], acc)
        | x :: l ->
            let x, acc = f x acc in
            let l, acc = loop f l acc in
            (x :: l, acc)
      in
      loop

    method array : 'a. ('a, 'acc) T.fold_map -> ('a array, 'acc) T.fold_map =
      fun f a acc ->
        let len = Array.length a in
        if len = 0 then (a, acc)
        else
          let x, acc = f (Array.unsafe_get a 0) acc in
          let a' = Array.make len x in
          let r = ref acc in
          for i = 1 to len - 1 do
            let x, acc = f (Array.unsafe_get a i) !r in
            Array.unsafe_set a' i x;
            r := acc
          done;
          (a', !r)
  end

class ['ctx] map_with_context =
  let any _ x = x in
  object
    method int : ('ctx, int) T.map_with_context = any
    method string : ('ctx, string) T.map_with_context = any
    method bool : ('ctx, bool) T.map_with_context = any
    method char : ('ctx, char) T.map_with_context = any

    method option :
        'a.
        ('ctx, 'a) T.map_with_context -> ('ctx, 'a option) T.map_with_context =
      fun f ctx x -> match x with None -> None | Some x -> Some (f ctx x)

    method list :
        'a. ('ctx, 'a) T.map_with_context -> ('ctx, 'a list) T.map_with_context
        =
      fun f ctx l -> List.map (f ctx) l

    method array :
        'a. ('ctx, 'a) T.map_with_context -> ('ctx, 'a array) T.map_with_context
        =
      fun f ctx a -> Array.map (f ctx) a
  end

class virtual ['res] lift =
  object (self)
    method virtual other : 'a. ('a, 'res) T.lift
    method virtual int : (int, 'res) T.lift
    method virtual string : (string, 'res) T.lift
    method virtual bool : (bool, 'res) T.lift
    method virtual char : (char, 'res) T.lift
    method virtual array : 'a. ('a, 'res) T.lift -> ('a array, 'res) T.lift
    method virtual float : (float, 'res) T.lift
    method virtual int32 : (int32, 'res) T.lift
    method virtual int64 : (int64, 'res) T.lift
    method virtual nativeint : (nativeint, 'res) T.lift
    method virtual unit : (unit, 'res) T.lift
    method virtual record : (string * 'res) list -> 'res
    method virtual constr : string -> 'res list -> 'res
    method virtual tuple : 'res list -> 'res

    method option : 'a. ('a, 'res) T.lift -> ('a option, 'res) T.lift =
      fun f x ->
        match x with
        | None -> self#constr "None" []
        | Some x -> self#constr "Some" [ f x ]

    method list : 'a. ('a, 'res) T.lift -> ('a list, 'res) T.lift =
      fun f l ->
        match l with
        | [] -> self#constr "[]" []
        | x :: l -> self#constr "::" [ f x; self#list f l ]
  end

class virtual ['ctx, 'res] lift_map_with_context =
  object (self)
    method virtual other : 'a. 'ctx -> 'a -> 'res
    method virtual int : ('ctx, int, 'res) T.lift_map_with_context
    method virtual string : ('ctx, string, 'res) T.lift_map_with_context
    method virtual bool : ('ctx, bool, 'res) T.lift_map_with_context
    method virtual char : ('ctx, char, 'res) T.lift_map_with_context

    method virtual array
        : 'a.
          ('ctx, 'a, 'res) T.lift_map_with_context ->
          ('ctx, 'a array, 'res) T.lift_map_with_context

    method virtual float : ('ctx, float, 'res) T.lift_map_with_context
    method virtual int32 : ('ctx, int32, 'res) T.lift_map_with_context
    method virtual int64 : ('ctx, int64, 'res) T.lift_map_with_context
    method virtual nativeint : ('ctx, nativeint, 'res) T.lift_map_with_context
    method virtual unit : ('ctx, unit, 'res) T.lift_map_with_context
    method virtual record : 'ctx -> (string * 'res) list -> 'res
    method virtual constr : 'ctx -> string -> 'res list -> 'res
    method virtual tuple : 'ctx -> 'res list -> 'res

    method option :
        'a.
        ('ctx, 'a, 'res) T.lift_map_with_context ->
        ('ctx, 'a option, 'res) T.lift_map_with_context =
      fun f ctx x ->
        match x with
        | None -> (None, self#constr ctx "None" [])
        | Some x ->
            let x, res = f ctx x in
            (Some x, self#constr ctx "Some" [ res ])

    method list :
        'a.
        ('ctx, 'a, 'res) T.lift_map_with_context ->
        ('ctx, 'a list, 'res) T.lift_map_with_context =
      fun f ctx l ->
        match l with
        | [] -> ([], self#constr ctx "[]" [])
        | x :: l ->
            let x, res_head = f ctx x in
            let l, res_tail = self#list f ctx l in
            (x :: l, self#constr ctx "::" [ res_head; res_tail ])
  end

class type ['res] std_lifters = object
  method other : 'a. ('a, 'res) T.lift
  method int : (int, 'res) T.lift
  method string : (string, 'res) T.lift
  method bool : (bool, 'res) T.lift
  method char : (char, 'res) T.lift
  method array : 'a. ('a, 'res) T.lift -> ('a array, 'res) T.lift
  method record : (string * 'res) list -> 'res
  method constr : string -> 'res list -> 'res
  method tuple : 'res list -> 'res
  method float : (float, 'res) T.lift
  method int32 : (int32, 'res) T.lift
  method int64 : (int64, 'res) T.lift
  method nativeint : (nativeint, 'res) T.lift
  method unit : (unit, 'res) T.lift
  method option : 'a. ('a, 'res) T.lift -> ('a option, 'res) T.lift
  method list : 'a. ('a, 'res) T.lift -> ('a list, 'res) T.lift
end

class type ['ctx, 'res] std_lift_mappers_with_context = object
  method other : 'a. 'ctx -> 'a -> 'res
  method int : ('ctx, int, 'res) T.lift_map_with_context
  method string : ('ctx, string, 'res) T.lift_map_with_context
  method bool : ('ctx, bool, 'res) T.lift_map_with_context
  method char : ('ctx, char, 'res) T.lift_map_with_context

  method array :
    'a.
    ('ctx, 'a, 'res) T.lift_map_with_context ->
    ('ctx, 'a array, 'res) T.lift_map_with_context

  method record : 'ctx -> (string * 'res) list -> 'res
  method constr : 'ctx -> string -> 'res list -> 'res
  method tuple : 'ctx -> 'res list -> 'res
  method float : ('ctx, float, 'res) T.lift_map_with_context
  method int32 : ('ctx, int32, 'res) T.lift_map_with_context
  method int64 : ('ctx, int64, 'res) T.lift_map_with_context
  method nativeint : ('ctx, nativeint, 'res) T.lift_map_with_context
  method unit : ('ctx, unit, 'res) T.lift_map_with_context

  method option :
    'a.
    ('ctx, 'a, 'res) T.lift_map_with_context ->
    ('ctx, 'a option, 'res) T.lift_map_with_context

  method list :
    'a.
    ('ctx, 'a, 'res) T.lift_map_with_context ->
    ('ctx, 'a list, 'res) T.lift_map_with_context
end
