(**************************************************************************)
(*                                                                        *)
(*                         OCaml Migrate Parsetree                        *)
(*                                                                        *)
(*                             Frédéric Bour                              *)
(*                   Jérémie Dimino, Jane Street Europe                   *)
(*                                                                        *)
(*   Copyright 2017 Institut National de Recherche en Informatique et     *)
(*     en Automatique (INRIA).                                            *)
(*                                                                        *)
(*   All rights reserved.  This file is distributed under the terms of    *)
(*   the GNU Lesser General Public License version 2.1, with the          *)
(*   special exception on linking described in the file LICENSE.          *)
(*                                                                        *)
(**************************************************************************)

(* BEGIN of BLACK MAGIC *)
(*$ open Ast_cinaps_helpers $*)

type _ witnesses = ..

type _ migration = ..
type _ migration += Undefined : _ migration

type 'a migration_info = {
  mutable next_version : 'a migration;
  mutable previous_version : 'a migration;
}

(** Abstract view of a version of an OCaml Ast *)
module type Ast = sig
  (*$ foreach_module (fun m types ->
      printf "  module %s : sig\n" m;
      List.iter types ~f:(printf "    type %s\n");
      printf "  end\n"
    )
  *)
  module Parsetree : sig
    type structure
    type signature
    type toplevel_phrase
    type core_type
    type expression
    type pattern
    type case
    type type_declaration
    type type_extension
    type extension_constructor
    type class_expr
    type class_field
    type class_type
    type class_signature
    type class_type_field
    type module_expr
    type module_type
    type signature_item
    type structure_item
  end
(*$*)
  module Config : sig
    val ast_impl_magic_number : string
    val ast_intf_magic_number : string
  end
end

(* Shortcuts for talking about ast types outside of the module language *)

type 'a _types = 'a constraint 'a
  = <
    (*$ foreach_type (fun _ s -> printf "    %-21s : _;\n" s) *)
    structure             : _;
    signature             : _;
    toplevel_phrase       : _;
    core_type             : _;
    expression            : _;
    pattern               : _;
    case                  : _;
    type_declaration      : _;
    type_extension        : _;
    extension_constructor : _;
    class_expr            : _;
    class_field           : _;
    class_type            : _;
    class_signature       : _;
    class_type_field      : _;
    module_expr           : _;
    module_type           : _;
    signature_item        : _;
    structure_item        : _;
(*$*)
  >
;;

(*$ foreach_type (fun _ s ->
    printf "type 'a get_%s =\n" s;
    printf "  'x constraint 'a _types = < %s : 'x; .. >\n" s
  ) *)
type 'a get_structure =
  'x constraint 'a _types = < structure : 'x; .. >
type 'a get_signature =
  'x constraint 'a _types = < signature : 'x; .. >
type 'a get_toplevel_phrase =
  'x constraint 'a _types = < toplevel_phrase : 'x; .. >
type 'a get_core_type =
  'x constraint 'a _types = < core_type : 'x; .. >
type 'a get_expression =
  'x constraint 'a _types = < expression : 'x; .. >
type 'a get_pattern =
  'x constraint 'a _types = < pattern : 'x; .. >
type 'a get_case =
  'x constraint 'a _types = < case : 'x; .. >
type 'a get_type_declaration =
  'x constraint 'a _types = < type_declaration : 'x; .. >
type 'a get_type_extension =
  'x constraint 'a _types = < type_extension : 'x; .. >
type 'a get_extension_constructor =
  'x constraint 'a _types = < extension_constructor : 'x; .. >
type 'a get_class_expr =
  'x constraint 'a _types = < class_expr : 'x; .. >
type 'a get_class_field =
  'x constraint 'a _types = < class_field : 'x; .. >
type 'a get_class_type =
  'x constraint 'a _types = < class_type : 'x; .. >
type 'a get_class_signature =
  'x constraint 'a _types = < class_signature : 'x; .. >
type 'a get_class_type_field =
  'x constraint 'a _types = < class_type_field : 'x; .. >
type 'a get_module_expr =
  'x constraint 'a _types = < module_expr : 'x; .. >
type 'a get_module_type =
  'x constraint 'a _types = < module_type : 'x; .. >
type 'a get_signature_item =
  'x constraint 'a _types = < signature_item : 'x; .. >
type 'a get_structure_item =
  'x constraint 'a _types = < structure_item : 'x; .. >
(*$*)

module type OCaml_version = sig
  module Ast : Ast
  val version : int
  val string_version : string
  type types = <
    (*$ foreach_type (fun m s -> printf "    %-21s : Ast.%s.%s;\n" s m s)*)
    structure             : Ast.Parsetree.structure;
    signature             : Ast.Parsetree.signature;
    toplevel_phrase       : Ast.Parsetree.toplevel_phrase;
    core_type             : Ast.Parsetree.core_type;
    expression            : Ast.Parsetree.expression;
    pattern               : Ast.Parsetree.pattern;
    case                  : Ast.Parsetree.case;
    type_declaration      : Ast.Parsetree.type_declaration;
    type_extension        : Ast.Parsetree.type_extension;
    extension_constructor : Ast.Parsetree.extension_constructor;
    class_expr            : Ast.Parsetree.class_expr;
    class_field           : Ast.Parsetree.class_field;
    class_type            : Ast.Parsetree.class_type;
    class_signature       : Ast.Parsetree.class_signature;
    class_type_field      : Ast.Parsetree.class_type_field;
    module_expr           : Ast.Parsetree.module_expr;
    module_type           : Ast.Parsetree.module_type;
    signature_item        : Ast.Parsetree.signature_item;
    structure_item        : Ast.Parsetree.structure_item;
(*$*)
  > _types
  type _ witnesses += Version : types witnesses
  val migration_info : types migration_info
end

module Make_witness(Ast : Ast) =
struct
  type types = <
    (*$ foreach_type (fun m s -> printf "    %-21s : Ast.%s.%s;\n" s m s)*)
    structure             : Ast.Parsetree.structure;
    signature             : Ast.Parsetree.signature;
    toplevel_phrase       : Ast.Parsetree.toplevel_phrase;
    core_type             : Ast.Parsetree.core_type;
    expression            : Ast.Parsetree.expression;
    pattern               : Ast.Parsetree.pattern;
    case                  : Ast.Parsetree.case;
    type_declaration      : Ast.Parsetree.type_declaration;
    type_extension        : Ast.Parsetree.type_extension;
    extension_constructor : Ast.Parsetree.extension_constructor;
    class_expr            : Ast.Parsetree.class_expr;
    class_field           : Ast.Parsetree.class_field;
    class_type            : Ast.Parsetree.class_type;
    class_signature       : Ast.Parsetree.class_signature;
    class_type_field      : Ast.Parsetree.class_type_field;
    module_expr           : Ast.Parsetree.module_expr;
    module_type           : Ast.Parsetree.module_type;
    signature_item        : Ast.Parsetree.signature_item;
    structure_item        : Ast.Parsetree.structure_item;
(*$*)
  > _types
  type _ witnesses += Version : types witnesses
  let migration_info : types migration_info =
    { next_version = Undefined; previous_version = Undefined }
end

type 'types ocaml_version =
  (module OCaml_version
    (*$ let sep = with_then_and () in
      foreach_type (fun m s ->
          printf "    %t type Ast.%s.%s = 'types get_%s\n" sep m s s) *)
    with type Ast.Parsetree.structure = 'types get_structure
     and type Ast.Parsetree.signature = 'types get_signature
     and type Ast.Parsetree.toplevel_phrase = 'types get_toplevel_phrase
     and type Ast.Parsetree.core_type = 'types get_core_type
     and type Ast.Parsetree.expression = 'types get_expression
     and type Ast.Parsetree.pattern = 'types get_pattern
     and type Ast.Parsetree.case = 'types get_case
     and type Ast.Parsetree.type_declaration = 'types get_type_declaration
     and type Ast.Parsetree.type_extension = 'types get_type_extension
     and type Ast.Parsetree.extension_constructor = 'types get_extension_constructor
     and type Ast.Parsetree.class_expr = 'types get_class_expr
     and type Ast.Parsetree.class_field = 'types get_class_field
     and type Ast.Parsetree.class_type = 'types get_class_type
     and type Ast.Parsetree.class_signature = 'types get_class_signature
     and type Ast.Parsetree.class_type_field = 'types get_class_type_field
     and type Ast.Parsetree.module_expr = 'types get_module_expr
     and type Ast.Parsetree.module_type = 'types get_module_type
     and type Ast.Parsetree.signature_item = 'types get_signature_item
     and type Ast.Parsetree.structure_item = 'types get_structure_item
(*$*)
  )

type ('from, 'to_) migration_functions = {
  (*$ foreach_type (fun _ s ->
      printf "  copy_%s: 'from get_%s -> 'to_ get_%s;\n" s s s) *)
  copy_structure: 'from get_structure -> 'to_ get_structure;
  copy_signature: 'from get_signature -> 'to_ get_signature;
  copy_toplevel_phrase: 'from get_toplevel_phrase -> 'to_ get_toplevel_phrase;
  copy_core_type: 'from get_core_type -> 'to_ get_core_type;
  copy_expression: 'from get_expression -> 'to_ get_expression;
  copy_pattern: 'from get_pattern -> 'to_ get_pattern;
  copy_case: 'from get_case -> 'to_ get_case;
  copy_type_declaration: 'from get_type_declaration -> 'to_ get_type_declaration;
  copy_type_extension: 'from get_type_extension -> 'to_ get_type_extension;
  copy_extension_constructor: 'from get_extension_constructor -> 'to_ get_extension_constructor;
  copy_class_expr: 'from get_class_expr -> 'to_ get_class_expr;
  copy_class_field: 'from get_class_field -> 'to_ get_class_field;
  copy_class_type: 'from get_class_type -> 'to_ get_class_type;
  copy_class_signature: 'from get_class_signature -> 'to_ get_class_signature;
  copy_class_type_field: 'from get_class_type_field -> 'to_ get_class_type_field;
  copy_module_expr: 'from get_module_expr -> 'to_ get_module_expr;
  copy_module_type: 'from get_module_type -> 'to_ get_module_type;
  copy_signature_item: 'from get_signature_item -> 'to_ get_signature_item;
  copy_structure_item: 'from get_structure_item -> 'to_ get_structure_item;
(*$*)
}

let id x = x
let migration_identity : ('a, 'a) migration_functions = {
  (*$ foreach_type (fun _ s -> printf "  copy_%s = id;\n" s) *)
  copy_structure = id;
  copy_signature = id;
  copy_toplevel_phrase = id;
  copy_core_type = id;
  copy_expression = id;
  copy_pattern = id;
  copy_case = id;
  copy_type_declaration = id;
  copy_type_extension = id;
  copy_extension_constructor = id;
  copy_class_expr = id;
  copy_class_field = id;
  copy_class_type = id;
  copy_class_signature = id;
  copy_class_type_field = id;
  copy_module_expr = id;
  copy_module_type = id;
  copy_signature_item = id;
  copy_structure_item = id;
(*$*)
}

let compose f g x = f (g x)
let migration_compose (ab : ('a, 'b) migration_functions) (bc : ('b, 'c) migration_functions) : ('a, 'c) migration_functions = {
  (*$ foreach_type (fun _ s ->
      printf "  copy_%-21s = compose bc.copy_%-21s ab.copy_%s;\n" s s s) *)
  copy_structure             = compose bc.copy_structure             ab.copy_structure;
  copy_signature             = compose bc.copy_signature             ab.copy_signature;
  copy_toplevel_phrase       = compose bc.copy_toplevel_phrase       ab.copy_toplevel_phrase;
  copy_core_type             = compose bc.copy_core_type             ab.copy_core_type;
  copy_expression            = compose bc.copy_expression            ab.copy_expression;
  copy_pattern               = compose bc.copy_pattern               ab.copy_pattern;
  copy_case                  = compose bc.copy_case                  ab.copy_case;
  copy_type_declaration      = compose bc.copy_type_declaration      ab.copy_type_declaration;
  copy_type_extension        = compose bc.copy_type_extension        ab.copy_type_extension;
  copy_extension_constructor = compose bc.copy_extension_constructor ab.copy_extension_constructor;
  copy_class_expr            = compose bc.copy_class_expr            ab.copy_class_expr;
  copy_class_field           = compose bc.copy_class_field           ab.copy_class_field;
  copy_class_type            = compose bc.copy_class_type            ab.copy_class_type;
  copy_class_signature       = compose bc.copy_class_signature       ab.copy_class_signature;
  copy_class_type_field      = compose bc.copy_class_type_field      ab.copy_class_type_field;
  copy_module_expr           = compose bc.copy_module_expr           ab.copy_module_expr;
  copy_module_type           = compose bc.copy_module_type           ab.copy_module_type;
  copy_signature_item        = compose bc.copy_signature_item        ab.copy_signature_item;
  copy_structure_item        = compose bc.copy_structure_item        ab.copy_structure_item;
(*$*)
}

type _ migration += Migration : 'from ocaml_version * ('from, 'to_) migration_functions * 'to_ ocaml_version -> 'from migration

module type Migrate_module = sig
  module From : Ast
  module To : Ast
  (*$ foreach_type (fun m s ->
      printf "  val copy_%-21s: From.%s.%s -> To.%s.%s\n" s m s m s) *)
  val copy_structure            : From.Parsetree.structure -> To.Parsetree.structure
  val copy_signature            : From.Parsetree.signature -> To.Parsetree.signature
  val copy_toplevel_phrase      : From.Parsetree.toplevel_phrase -> To.Parsetree.toplevel_phrase
  val copy_core_type            : From.Parsetree.core_type -> To.Parsetree.core_type
  val copy_expression           : From.Parsetree.expression -> To.Parsetree.expression
  val copy_pattern              : From.Parsetree.pattern -> To.Parsetree.pattern
  val copy_case                 : From.Parsetree.case -> To.Parsetree.case
  val copy_type_declaration     : From.Parsetree.type_declaration -> To.Parsetree.type_declaration
  val copy_type_extension       : From.Parsetree.type_extension -> To.Parsetree.type_extension
  val copy_extension_constructor: From.Parsetree.extension_constructor -> To.Parsetree.extension_constructor
  val copy_class_expr           : From.Parsetree.class_expr -> To.Parsetree.class_expr
  val copy_class_field          : From.Parsetree.class_field -> To.Parsetree.class_field
  val copy_class_type           : From.Parsetree.class_type -> To.Parsetree.class_type
  val copy_class_signature      : From.Parsetree.class_signature -> To.Parsetree.class_signature
  val copy_class_type_field     : From.Parsetree.class_type_field -> To.Parsetree.class_type_field
  val copy_module_expr          : From.Parsetree.module_expr -> To.Parsetree.module_expr
  val copy_module_type          : From.Parsetree.module_type -> To.Parsetree.module_type
  val copy_signature_item       : From.Parsetree.signature_item -> To.Parsetree.signature_item
  val copy_structure_item       : From.Parsetree.structure_item -> To.Parsetree.structure_item
(*$*)
end

module Migration_functions
    (A : OCaml_version) (B : OCaml_version)
    (A_to_B : Migrate_module with module From = A.Ast and module To = B.Ast)
=
struct
  let migration_functions : (A.types, B.types) migration_functions =
    let open A_to_B in
    {
      (*$ foreach_type (fun _ s -> printf "      copy_%s;\n" s) *)
      copy_structure;
      copy_signature;
      copy_toplevel_phrase;
      copy_core_type;
      copy_expression;
      copy_pattern;
      copy_case;
      copy_type_declaration;
      copy_type_extension;
      copy_extension_constructor;
      copy_class_expr;
      copy_class_field;
      copy_class_type;
      copy_class_signature;
      copy_class_type_field;
      copy_module_expr;
      copy_module_type;
      copy_signature_item;
      copy_structure_item;
(*$*)
    }
end

module Register_migration (A : OCaml_version) (B : OCaml_version)
    (A_to_B : Migrate_module with module From = A.Ast and module To = B.Ast)
    (B_to_A : Migrate_module with module From = B.Ast and module To = A.Ast)
=
struct
  let () = (
    let is_undefined : type a. a migration -> bool = function
      | Undefined -> true
      | _ -> false
    in
    assert (A.version < B.version);
    assert (is_undefined A.migration_info.next_version);
    assert (is_undefined B.migration_info.previous_version);
    let module A_to_B_fun = Migration_functions(A)(B)(A_to_B) in
    let module B_to_A_fun = Migration_functions(B)(A)(B_to_A) in
    A.migration_info.next_version <-
      Migration ((module A), A_to_B_fun.migration_functions, (module B));
    B.migration_info.previous_version <-
      Migration ((module B), B_to_A_fun.migration_functions, (module A));
  )
end

type 'from immediate_migration =
  | No_migration : 'from immediate_migration
  | Immediate_migration
    :  ('from, 'to_) migration_functions * 'to_ ocaml_version
      -> 'from immediate_migration

let immediate_migration
    (*$ foreach_type (fun _ s -> printf "    (type %s)\n" s) *)
    (type structure)
    (type signature)
    (type toplevel_phrase)
    (type core_type)
    (type expression)
    (type pattern)
    (type case)
    (type type_declaration)
    (type type_extension)
    (type extension_constructor)
    (type class_expr)
    (type class_field)
    (type class_type)
    (type class_signature)
    (type class_type_field)
    (type module_expr)
    (type module_type)
    (type signature_item)
    (type structure_item)
(*$*)
    ((module A) : <
     (*$ foreach_type (fun _ s -> printf  "     %-21s : %s;\n" s s) *)
     structure             : structure;
     signature             : signature;
     toplevel_phrase       : toplevel_phrase;
     core_type             : core_type;
     expression            : expression;
     pattern               : pattern;
     case                  : case;
     type_declaration      : type_declaration;
     type_extension        : type_extension;
     extension_constructor : extension_constructor;
     class_expr            : class_expr;
     class_field           : class_field;
     class_type            : class_type;
     class_signature       : class_signature;
     class_type_field      : class_type_field;
     module_expr           : module_expr;
     module_type           : module_type;
     signature_item        : signature_item;
     structure_item        : structure_item;
(*$*)
     > ocaml_version)
    direction
  =
  let version = match direction with
    | `Next -> A.migration_info.next_version
    | `Previous -> A.migration_info.previous_version
  in
  match version with
  | Undefined -> No_migration
  | Migration (_, funs, to_) -> Immediate_migration (funs, to_)
  | _ -> assert false

let migrate
    (*$ foreach_type (fun _ s -> printf "    (type %s1) (type %s2)\n" s s) *)
    (type structure1) (type structure2)
    (type signature1) (type signature2)
    (type toplevel_phrase1) (type toplevel_phrase2)
    (type core_type1) (type core_type2)
    (type expression1) (type expression2)
    (type pattern1) (type pattern2)
    (type case1) (type case2)
    (type type_declaration1) (type type_declaration2)
    (type type_extension1) (type type_extension2)
    (type extension_constructor1) (type extension_constructor2)
    (type class_expr1) (type class_expr2)
    (type class_field1) (type class_field2)
    (type class_type1) (type class_type2)
    (type class_signature1) (type class_signature2)
    (type class_type_field1) (type class_type_field2)
    (type module_expr1) (type module_expr2)
    (type module_type1) (type module_type2)
    (type signature_item1) (type signature_item2)
    (type structure_item1) (type structure_item2)
(*$*)
    ((module A) : <
     (*$ foreach_type (fun _ s -> printf "     %-21s : %s1;\n" s s) *)
     structure             : structure1;
     signature             : signature1;
     toplevel_phrase       : toplevel_phrase1;
     core_type             : core_type1;
     expression            : expression1;
     pattern               : pattern1;
     case                  : case1;
     type_declaration      : type_declaration1;
     type_extension        : type_extension1;
     extension_constructor : extension_constructor1;
     class_expr            : class_expr1;
     class_field           : class_field1;
     class_type            : class_type1;
     class_signature       : class_signature1;
     class_type_field      : class_type_field1;
     module_expr           : module_expr1;
     module_type           : module_type1;
     signature_item        : signature_item1;
     structure_item        : structure_item1;
(*$*)
     > ocaml_version)
    ((module B) : <
     (*$ foreach_type (fun _ s -> printf "     %-21s : %s2;\n" s s) *)
     structure             : structure2;
     signature             : signature2;
     toplevel_phrase       : toplevel_phrase2;
     core_type             : core_type2;
     expression            : expression2;
     pattern               : pattern2;
     case                  : case2;
     type_declaration      : type_declaration2;
     type_extension        : type_extension2;
     extension_constructor : extension_constructor2;
     class_expr            : class_expr2;
     class_field           : class_field2;
     class_type            : class_type2;
     class_signature       : class_signature2;
     class_type_field      : class_type_field2;
     module_expr           : module_expr2;
     module_type           : module_type2;
     signature_item        : signature_item2;
     structure_item        : structure_item2;
(*$*)
     > ocaml_version)
  : (A.types, B.types) migration_functions
  =
  match A.Version with
  | B.Version -> migration_identity
  | _ ->
    let direction = if A.version < B.version then `Next else `Previous in
    let rec migrate (m : A.types immediate_migration) : (A.types, B.types) migration_functions =
      match m with
      | No_migration -> assert false
      | Immediate_migration (f, (module To)) ->
        match To.Version with
        | B.Version -> f
        | _ ->
          match immediate_migration (module To) direction with
          | No_migration -> assert false
          | Immediate_migration (g, to2) ->
            migrate (Immediate_migration (migration_compose f g, to2))
    in
    migrate (immediate_migration (module A) direction)

module Convert (A : OCaml_version) (B : OCaml_version) = struct
  let {
    (*$ foreach_type (fun _ s -> printf "    copy_%s;\n" s) *)
    copy_structure;
    copy_signature;
    copy_toplevel_phrase;
    copy_core_type;
    copy_expression;
    copy_pattern;
    copy_case;
    copy_type_declaration;
    copy_type_extension;
    copy_extension_constructor;
    copy_class_expr;
    copy_class_field;
    copy_class_type;
    copy_class_signature;
    copy_class_type_field;
    copy_module_expr;
    copy_module_type;
    copy_signature_item;
    copy_structure_item;
(*$*)
  } : (A.types, B.types) migration_functions =
    migrate (module A) (module B)
end

(*$ foreach_version (fun n version ->
    printf "module OCaml_%d = struct\n" n;
    printf "  module Ast = Astlib.Ast_%d\n" n;
    printf "  include Make_witness(Astlib.Ast_%d)\n" n;
    printf "  let version = %d\n" n;
    printf "  let string_version = %S\n" version;
    printf "end\n";
    printf "let ocaml_%d : OCaml_%d.types ocaml_version = (module OCaml_%d)\n"
      n n n;
  )
*)
module OCaml_408 = struct
  module Ast = Astlib.Ast_408
  include Make_witness(Astlib.Ast_408)
  let version = 408
  let string_version = "4.08"
end
let ocaml_408 : OCaml_408.types ocaml_version = (module OCaml_408)
module OCaml_409 = struct
  module Ast = Astlib.Ast_409
  include Make_witness(Astlib.Ast_409)
  let version = 409
  let string_version = "4.09"
end
let ocaml_409 : OCaml_409.types ocaml_version = (module OCaml_409)
module OCaml_410 = struct
  module Ast = Astlib.Ast_410
  include Make_witness(Astlib.Ast_410)
  let version = 410
  let string_version = "4.10"
end
let ocaml_410 : OCaml_410.types ocaml_version = (module OCaml_410)
module OCaml_411 = struct
  module Ast = Astlib.Ast_411
  include Make_witness(Astlib.Ast_411)
  let version = 411
  let string_version = "4.11"
end
let ocaml_411 : OCaml_411.types ocaml_version = (module OCaml_411)
module OCaml_412 = struct
  module Ast = Astlib.Ast_412
  include Make_witness(Astlib.Ast_412)
  let version = 412
  let string_version = "4.12"
end
let ocaml_412 : OCaml_412.types ocaml_version = (module OCaml_412)
module OCaml_413 = struct
  module Ast = Astlib.Ast_413
  include Make_witness(Astlib.Ast_413)
  let version = 413
  let string_version = "4.13"
end
let ocaml_413 : OCaml_413.types ocaml_version = (module OCaml_413)
module OCaml_414 = struct
  module Ast = Astlib.Ast_414
  include Make_witness(Astlib.Ast_414)
  let version = 414
  let string_version = "4.14"
end
let ocaml_414 : OCaml_414.types ocaml_version = (module OCaml_414)
module OCaml_500 = struct
  module Ast = Astlib.Ast_500
  include Make_witness(Astlib.Ast_500)
  let version = 500
  let string_version = "5.0"
end
let ocaml_500 : OCaml_500.types ocaml_version = (module OCaml_500)
module OCaml_501 = struct
  module Ast = Astlib.Ast_501
  include Make_witness(Astlib.Ast_501)
  let version = 501
  let string_version = "5.1"
end
let ocaml_501 : OCaml_501.types ocaml_version = (module OCaml_501)
module OCaml_502 = struct
  module Ast = Astlib.Ast_502
  include Make_witness(Astlib.Ast_502)
  let version = 502
  let string_version = "5.2"
end
let ocaml_502 : OCaml_502.types ocaml_version = (module OCaml_502)
module OCaml_503 = struct
  module Ast = Astlib.Ast_503
  include Make_witness(Astlib.Ast_503)
  let version = 503
  let string_version = "5.3"
end
let ocaml_503 : OCaml_503.types ocaml_version = (module OCaml_503)
module OCaml_504 = struct
  module Ast = Astlib.Ast_504
  include Make_witness(Astlib.Ast_504)
  let version = 504
  let string_version = "5.4"
end
let ocaml_504 : OCaml_504.types ocaml_version = (module OCaml_504)
(*$*)

let all_versions : (module OCaml_version) list = [
  (*$foreach_version (fun n _ ->
      printf "(module OCaml_%d : OCaml_version);\n" n)*)
(module OCaml_408 : OCaml_version);
(module OCaml_409 : OCaml_version);
(module OCaml_410 : OCaml_version);
(module OCaml_411 : OCaml_version);
(module OCaml_412 : OCaml_version);
(module OCaml_413 : OCaml_version);
(module OCaml_414 : OCaml_version);
(module OCaml_500 : OCaml_version);
(module OCaml_501 : OCaml_version);
(module OCaml_502 : OCaml_version);
(module OCaml_503 : OCaml_version);
(module OCaml_504 : OCaml_version);
(*$*)
]

(*$foreach_version_pair (fun a b ->
    printf "include Register_migration(OCaml_%d)(OCaml_%d)\n" a b;
    printf "    (Astlib.Migrate_%d_%d)(Astlib.Migrate_%d_%d)\n" a b b a
  )
*)
include Register_migration(OCaml_408)(OCaml_409)
    (Astlib.Migrate_408_409)(Astlib.Migrate_409_408)
include Register_migration(OCaml_409)(OCaml_410)
    (Astlib.Migrate_409_410)(Astlib.Migrate_410_409)
include Register_migration(OCaml_410)(OCaml_411)
    (Astlib.Migrate_410_411)(Astlib.Migrate_411_410)
include Register_migration(OCaml_411)(OCaml_412)
    (Astlib.Migrate_411_412)(Astlib.Migrate_412_411)
include Register_migration(OCaml_412)(OCaml_413)
    (Astlib.Migrate_412_413)(Astlib.Migrate_413_412)
include Register_migration(OCaml_413)(OCaml_414)
    (Astlib.Migrate_413_414)(Astlib.Migrate_414_413)
include Register_migration(OCaml_414)(OCaml_500)
    (Astlib.Migrate_414_500)(Astlib.Migrate_500_414)
include Register_migration(OCaml_500)(OCaml_501)
    (Astlib.Migrate_500_501)(Astlib.Migrate_501_500)
include Register_migration(OCaml_501)(OCaml_502)
    (Astlib.Migrate_501_502)(Astlib.Migrate_502_501)
include Register_migration(OCaml_502)(OCaml_503)
    (Astlib.Migrate_502_503)(Astlib.Migrate_503_502)
include Register_migration(OCaml_503)(OCaml_504)
    (Astlib.Migrate_503_504)(Astlib.Migrate_504_503)
(*$*)

module OCaml_current = OCaml_OCAML_VERSION

module Find_version = struct
  type t = Impl of (module OCaml_version) | Intf of (module OCaml_version) | Unknown

  let from_magic magic =
    let rec loop = function
      | [] -> Unknown
      | (module Version : OCaml_version) :: tail ->
          if Version.Ast.Config.ast_impl_magic_number = magic then
            Impl (module Version)
          else if Version.Ast.Config.ast_intf_magic_number = magic then
            Intf (module Version)
          else
            loop tail
    in
    (* Traverse the versions from last to first:
       if the magic numbers aren't unique among versions,
       we want the latest version with a magic number match.
       The situation in mind is trunk support. *)
      let all_versions_top_down = List.rev all_versions in
      loop all_versions_top_down
end
