/*++
Copyright (c) Microsoft Corporation

Module Name:

    Z3

Abstract:

    OCaml API for Z3.

    The following design is used for the treatment of reference counting:

    - The conversion of Z3_context from ML to C remembers the Z3 context, and
      registers a finalizer using Gc.finalize that calls Z3_del_context.

    - The conversion of Z3_ast and other reference counted types from C to ML:

      + stores the last translated context with the Z3_ast in the wrapper
        object;

      + registers a finalizer using Gc.finalize that decrements the reference
        counter of the Z3_ast;

      + increments the reference count of the Z3_ast.

      The finalizers are registered using (the C interface to) Gc.finalize,
      not attaching a finalizer to a custom block.  The finalizers registered
      by Gc.finalize are guaranteed to be called in reverse
      registration-order, which is necessary to ensure that Z3_context's are
      finalized only after all the Z3_ast's within them.

    - ML Z3.ast (and subtypes) are given generic hash and comparison
      operations using Z3_get_ast_hash and Z3_get_ast_id.  Other types could
      be handled similarly if analogous hash and id operations were exported
      by the C API.

    - The wrapper for Z3_mk_context is customized (using quote(call,...) in
      z3_api.patched.h) to call Z3_mk_context_rc, and the ML API does not
      include mk_context_rc.

    This scheme relies on the property that all reference counted values
    returned from C to ML are in the Z3_context that was last sent from ML to
    C.  This is normally straightforward, but note that it depends on the
    argument order of e.g. the Z3_*translate functions.

    Non-reference counted Z3 types that have delete operations have finalizers
    that call the delete operations.  The exposed delete operations are
    shadowed by nop functions.  The types whose delete operation accepts a
    context use Gc.finalize while those that do not use custom block
    finalizers.

    Custom c2ml functions check the Z3 error code prior to allocating ML
    values or registering finalizers.  Other functions check the Z3 error code
    after making a Z3 library call.

    Some operations return NULL pointers when operations fail, or accept NULL
    pointers.  To handle these cases Z3_{ast,func_interp,sort}_opt types are
    introduced.  These are synonyms of Z3_{ast,func_interp,sort} but are
    translated into OCaml option types.  If the NULL pointers were passed to
    ML, even if the user does not access them, they will have finalizers
    registered, so when they die the OCaml GC will crash trying to call
    dec_ref on NULL.

    There is an alternate implementation, enabled by setting LEAK_CONTEXTS,
    that avoids the overhead of Gc.finalize finalizers, but at the price of
    leaking Z3_context objects.

Notes:

    OCaml does not support unsigned types, so CamlIDL conflates signed and
    unsigned types of the same size.  Therefore, functions in the C API
    operating on unsigned values that become redundant after this conflation
    are excluded from the ML API using [#ifndef CAMLIDL] in z3_api.h.

    CamlIDL does not support function pointers, so functions in the C API with
    function pointer arguments are handled manually.

Author:

    Jakob Lichtenberg (JakobL) 2007-08-08
    Josh Berdine (jjb) 2012-03-21

--*/


// cpp trick to include expanded macro arguments in string literals
#define xstr(s) str(s)
#define str(s) #s
quote(c,"#define xstr(s) str(s)");
quote(c,"#define str(s) #s");


// CamlIDL (1.05) has a bug where it does not accept [unsigned] as a type,
// only as a specifier, so unsigned is defined to be unsigned int.
#define unsigned unsigned int


// Suppress "warning C4090: 'function' : different 'const' qualifiers" as
// CamlIDL does not seem to get this right.
quote(c,"#pragma warning(disable:4090)");


#ifndef MLAPIV3

#define DEFINE_TYPE(T) typedef [abstract] void* T
#define DEFINE_VOID(T) typedef [abstract] void* T

#define BEGIN_MLAPI_EXCLUDE quote(mli,"(*");
#define END_MLAPI_EXCLUDE quote(mli,"*)");

#ifdef LEAK_CONTEXTS

  // Declare pointer type with custom conversion functions.
  #define DEFINE_CUST_TYPE(T) \
    typedef [abstract, ml2c(ml2c_Z3_ ## T), c2ml(c2ml_Z3_ ## T)] void* Z3_ ## T

#else
  
  // Declare pointer type with custom conversion functions.
  // Register an OCaml closure that just calls a C finalization function.
  #define DEFINE_CUST_TYPE(T) \
    quote(ml,xstr(\
      external finalize_Z3_ ## T : T -> unit = xstr(finalize_Z3_ ## T);; \
      let _ = Callback.register xstr(finalize_Z3_ ## T) finalize_Z3_ ## T \
    )); \
    typedef [abstract, ml2c(ml2c_Z3_ ## T), c2ml(c2ml_Z3_ ## T)] void* Z3_ ## T

#endif


// Z3_context
quote(c,"
void check_error_code (Z3_context c);

Z3_context last_ctx;
");

#ifdef LEAK_CONTEXTS

  quote(c,"
  value c2ml_Z3_context(Z3_context* c)
  {
    value v;
    v = caml_alloc_small(1, Abstract_tag);
    *((Z3_context *) Bp_val(v)) = *c;
    return v;
  }

  void ml2c_Z3_context(value v, Z3_context* c)
  {
    *c = *((Z3_context *) Bp_val(v));
    last_ctx = *c;
  }
  ");

#else

  quote(c,"
  // caml_final_register is the implementation of Gc.finalize
  value caml_final_register (value f, value v);

  void register_finalizer(value** closure, char* name, Z3_context ctx, value v)
  {
    if (*closure == NULL) {
      *closure = caml_named_value(name);
      if (*closure == NULL) {
        Z3_set_error(ctx, Z3_INTERNAL_FATAL);
        return;
      }
    }
    caml_final_register(**closure, v);
  }

  value c2ml_Z3_context (Z3_context* c)
  {
    static value* finalize_Z3_context_closure = NULL;
    value v;
    v = caml_alloc_small(1, Abstract_tag);
    Field(v, 0) = (value) *c;
    register_finalizer(&finalize_Z3_context_closure, \"finalize_Z3_context\",
                       (Z3_context) *c, v);
    return v;
  }

  void ml2c_Z3_context (value v, Z3_context* c)
  {
    *c = (Z3_context) Field(v, 0);
    last_ctx = *c;
  }

  value finalize_Z3_context (value v)
  {
    Z3_context c;
    c = (Z3_context) Field(v, 0);
    Z3_del_context(c);
    return Val_unit;
  }
  ");

#endif

DEFINE_CUST_TYPE(context);


// Z3_symbol
typedef [abstract] void* Z3_symbol;


// Z3_ast: reference counted type with hashing and comparison
quote(c,"
typedef struct _Z3_ast_context {
  Z3_ast ast;
  Z3_context ctx;
} Z3_ast_context;

void ml2c_Z3_ast (value v, Z3_ast* c)
{
  *c = ((Z3_ast_context*) Data_custom_val(v))->ast;
}

static int compare_Z3_ast (value v1, value v2)
{
  Z3_ast_context* ac1;
  Z3_ast_context* ac2;
  unsigned id1, id2;
  ac1 = Data_custom_val(v1);
  ac2 = Data_custom_val(v2);
  id1 = Z3_get_ast_id(ac1->ctx, ac1->ast);
  check_error_code(ac1->ctx);
  id2 = Z3_get_ast_id(ac2->ctx, ac2->ast);
  check_error_code(ac2->ctx);
  return id2 - id1;
}

static intnat hash_Z3_ast (value v)
{
  Z3_ast_context* ac;
  unsigned hash;
  ac = Data_custom_val(v);
  hash = Z3_get_ast_hash(ac->ctx, ac->ast);
  check_error_code(ac->ctx);
  return hash;
}
");

#ifdef LEAK_CONTEXTS

  quote(c,"
  static void finalize_Z3_ast (value v)
  {
    Z3_ast_context* ac;
    ac = Data_custom_val(v);
    Z3_dec_ref(ac->ctx, ac->ast);
    check_error_code(ac->ctx);
  }

  static struct custom_operations cops_Z3_ast = {
    NULL,
    finalize_Z3_ast,
    compare_Z3_ast,
    hash_Z3_ast,
    custom_serialize_default,
    custom_deserialize_default
  };

  value c2ml_Z3_ast (Z3_ast* c)
  {
    value v;
    Z3_ast_context* ac;
    check_error_code(last_ctx);
    v = alloc_custom(&cops_Z3_ast, sizeof(Z3_ast_context), 0, 1);
    ac = Data_custom_val(v);
    ac->ctx = last_ctx;
    ac->ast = *c;
    Z3_inc_ref(ac->ctx, ac->ast);
    return v;
  }
  ");

#else

  quote(c,"
  value finalize_Z3_ast (value v)
  {
    Z3_ast_context* ac;
    ac = Data_custom_val(v);
    Z3_dec_ref(ac->ctx, ac->ast);
    check_error_code(ac->ctx);
    return Val_unit;
  }

  static struct custom_operations cops_Z3_ast = {
    NULL,
    custom_finalize_default,
    compare_Z3_ast,
    hash_Z3_ast,
    custom_serialize_default,
    custom_deserialize_default
  };

  value c2ml_Z3_ast (Z3_ast* c)
  {
    static value* finalize_Z3_ast_closure = NULL;
    value v;
    Z3_ast_context* ac;
    check_error_code(last_ctx);
    v = caml_alloc_custom(&cops_Z3_ast, sizeof(Z3_ast_context), 0, 1);
    ac = Data_custom_val(v);
    ac->ast = *c;
    ac->ctx = last_ctx;
    register_finalizer(&finalize_Z3_ast_closure, \"finalize_Z3_ast\",
                       (Z3_context) *c, v);
    Z3_inc_ref(last_ctx, *c);
    return v;
  }
  ");

#endif

DEFINE_CUST_TYPE(ast);


// subtypes of Z3_ast
quote(c,"\
#define DEFINE_SUBAST_OPS(T) \
  void ml2c_ ## T (value v, T * a) \
  { \
    ml2c_Z3_ast(v, (Z3_ast*) a); \
  } \
 \
  value c2ml_ ## T (T * a) \
  { \
    return c2ml_Z3_ast((Z3_ast*) a); \
  } \
");

#define DEFINE_SUBAST(T) \
  typedef [mltype("private ast"), ml2c(ml2c_ ## T), c2ml(c2ml_ ## T)] Z3_ast T

quote(c,"DEFINE_SUBAST_OPS(Z3_sort)");		DEFINE_SUBAST(Z3_sort);
quote(c,"DEFINE_SUBAST_OPS(Z3_func_decl)");	DEFINE_SUBAST(Z3_func_decl);
quote(c,"DEFINE_SUBAST_OPS(Z3_app)");		DEFINE_SUBAST(Z3_app);
quote(c,"DEFINE_SUBAST_OPS(Z3_pattern)");	DEFINE_SUBAST(Z3_pattern);


// reference counted types without hashing and comparison
#ifdef LEAK_CONTEXTS

  quote(c,"\
  #define DEFINE_RC_OPS(T) \
    typedef struct _ ## T ## _context { \
      T dat; \
      Z3_context ctx; \
    } T ## _context; \
   \
    static void finalize_ ## T (value v) \
    { \
      T ## _context* ac; \
      ac = Data_custom_val(v); \
      T ## _dec_ref(ac->ctx, ac->dat); \
      check_error_code(ac->ctx); \
    } \
 \
    static struct custom_operations cops_ ## T = { \
      NULL, \
      finalize_ ## T, \
      custom_compare_default, \
      custom_hash_default, \
      custom_serialize_default, \
      custom_deserialize_default \
    }; \
 \
    value c2ml_ ## T (T * c) \
    { \
      value v; \
      T ## _context* ac; \
      check_error_code(last_ctx); \
      v = alloc_custom(&cops_ ## T, sizeof(T ## _context), 0, 1); \
      ac = Data_custom_val(v); \
      ac->dat = *c; \
      ac->ctx = last_ctx; \
      T ## _inc_ref(ac->ctx, ac->dat); \
      return v; \
    } \
 \
    void ml2c_ ## T (value v, T * c) \
    { \
      *c = ((T ## _context*) Data_custom_val(v))->dat; \
    } \
  ");

#else

  quote(c,"\
  #define DEFINE_RC_OPS(T) \
    value c2ml_ ## T (T * c) \
    { \
      static value* finalize_ ## T ## _closure = NULL; \
      value v; \
      check_error_code(last_ctx); \
      v = caml_alloc_small(2, Abstract_tag); \
      Field(v, 0) = (value) *c; \
      Field(v, 1) = (value) last_ctx; \
      register_finalizer(&finalize_ ## T ## _closure, xstr(finalize_ ## T), \
                         (Z3_context) *c, v); \
      T ## _inc_ref(last_ctx, *c); \
      return v; \
    } \
   \
    void ml2c_ ## T (value v, T * c) \
    { \
      *c = (T) Field(v, 0); \
    } \
   \
    value finalize_ ## T (value v) \
    { \
      Z3_context c; \
      c = (Z3_context) Field(v, 1); \
      T ## _dec_ref(c, (T) Field(v, 0)); \
      check_error_code(c); \
      return Val_unit; \
    } \
  ");

#endif

quote(c,"DEFINE_RC_OPS(Z3_params)");	   DEFINE_CUST_TYPE(params);
quote(c,"DEFINE_RC_OPS(Z3_param_descrs)");	   DEFINE_CUST_TYPE(param_descrs);
quote(c,"DEFINE_RC_OPS(Z3_model)");	   DEFINE_CUST_TYPE(model);
quote(c,"DEFINE_RC_OPS(Z3_func_interp)");  DEFINE_CUST_TYPE(func_interp);
quote(c,"DEFINE_RC_OPS(Z3_func_entry)");   DEFINE_CUST_TYPE(func_entry);
quote(c,"DEFINE_RC_OPS(Z3_fixedpoint)");   DEFINE_CUST_TYPE(fixedpoint);
quote(c,"DEFINE_RC_OPS(Z3_ast_vector)");   DEFINE_CUST_TYPE(ast_vector);
quote(c,"DEFINE_RC_OPS(Z3_ast_map)");	   DEFINE_CUST_TYPE(ast_map);
quote(c,"DEFINE_RC_OPS(Z3_goal)");	   DEFINE_CUST_TYPE(goal);
quote(c,"DEFINE_RC_OPS(Z3_tactic)");	   DEFINE_CUST_TYPE(tactic);
quote(c,"DEFINE_RC_OPS(Z3_probe)");	   DEFINE_CUST_TYPE(probe);
quote(c,"DEFINE_RC_OPS(Z3_apply_result)"); DEFINE_CUST_TYPE(apply_result);
quote(c,"DEFINE_RC_OPS(Z3_solver)");	   DEFINE_CUST_TYPE(solver);
quote(c,"DEFINE_RC_OPS(Z3_stats)");	   DEFINE_CUST_TYPE(stats);


// possibly-NULL pointer types, translated to OCaml option types
quote(c,"\
#define DEFINE_OPT_OPS(T) \
  void ml2c_ ## T ## _opt (value v, T* c) \
  { \
    struct camlidl_ctx_struct _ctxs = { CAMLIDL_TRANSIENT, NULL }; \
    camlidl_ctx _ctx = &_ctxs; \
    if (v != Val_int(0)) { \
      camlidl_ml2c_z3_ ## T(Field(v, 0), c, _ctx); \
    } else { \
      *c = NULL; \
    } \
  } \
 \
  value c2ml_ ## T ## _opt (T* c) \
  { \
    struct camlidl_ctx_struct _ctxs = { CAMLIDL_TRANSIENT, NULL }; \
    camlidl_ctx _ctx = &_ctxs; \
    value v; \
    value a; \
    if (*c) { \
      a = camlidl_c2ml_z3_ ## T(c, _ctx); \
      Begin_root(a) \
        v = caml_alloc_small(1, 0); \
        Field(v, 0) = a; \
      End_roots(); \
    } else { \
      v = Val_int(0); \
    } \
    return v; \
  }
");

#define DEFINE_OPT_TYPE(T) \
  typedef [mltype(xstr(T option)), \
           ml2c(ml2c_Z3_ ## T ## _opt), \
           c2ml(c2ml_Z3_ ## T ## _opt) \
          ] Z3_ ## T Z3_ ## T ## _opt

quote(c,"DEFINE_OPT_OPS(Z3_ast)");
DEFINE_OPT_TYPE(ast);

quote(c,"DEFINE_OPT_OPS(Z3_sort)");
DEFINE_OPT_TYPE(sort);

quote(c,"DEFINE_OPT_OPS(Z3_func_interp)");
DEFINE_OPT_TYPE(func_interp);


// ToDo: these unnecessarily appear in the API documentation
DEFINE_TYPE(Z3_constructor);
DEFINE_TYPE(Z3_constructor_list);


// shadow delete operations with nops
quote(ml,"
let del_constructor _ _ = ()
let del_constructor_list _ _ = ()
let del_model _ _ = ()
let del_context _ = ()
let reset_memory () = ()
");


#else // MLAPIV3

// Provide custom error handler:
quote (c,"Z3_error_handler caml_z3_error_handler;");
quote (c,"void caml_z3_error_handler(Z3_context c, Z3_error_code e) { static char buffer[128]; char * msg = Z3_get_error_msg_ex(c, e); if (strlen(msg) > 100) { failwith(\"Z3: error message is too big to fit in buffer\"); } else { sprintf(buffer, \"Z3: %s\", msg); failwith(buffer); } }");


#define DEFINE_TYPE(T) typedef [abstract] void* T
#define DEFINE_VOID(T) typedef [abstract] void* T

#define BEGIN_MLAPI_EXCLUDE
#define END_MLAPI_EXCLUDE

#endif // MLAPIV3



#ifndef __in
#define __in [in]
#endif

#ifndef __out
#define __out [out]
#endif

#ifndef __out_opt
#define __out_opt [out,unique]
#endif

#ifndef __ecount
#define __ecount(num_args) [NOT_SUPPORTED]
#endif

#ifndef __in_ecount
#define __in_ecount(num_args) [in, size_is(num_args), length_is(num_args)]
#endif

#ifndef __out_ecount
#define __out_ecount(num_args) [out, size_is(num_args), length_is(num_args)]
#endif

#ifndef __inout_ecount
#define __inout_ecount(num_args) [in, out, size_is(num_args), length_is(num_args)]
#endif

#ifndef __inout
#define __inout [in, out]
#endif

#ifndef Z3_bool_opt
#define Z3_bool_opt void
#endif


#define Z3_API

#ifdef MLAPIV3

#include "z3V3_api.idl"
#include "x3V3.mli"
#include "x3V3.ml"

#else

#include "z3_api.idl"
#include "x3.ml"

quote(ml,"
let _ =
  Printexc.register_printer (function
    | Error(c,e) -> Some (\"Z3 \"^(get_error_msg c e))
    | _ -> None
  )
");


quote(mli,"
(** 
   {2 {L Legacy V3 API}} 
*) 

module V3 : sig
(** 
   {2 {L Legacy V3 API}} 
*) 
");

quote(ml,"
module V3 = struct
");

#endif

#ifdef MLAPIV3

quote(mlmli,"
end
");

#endif
