5

I am currently looking into implementing a cleaner way to call native C functions from the Gravity scripting language.

So far, the most simplistic example would be this one:

int add(int lhs, int rhs) {
  return lhs + rhs;
}

static void gravity_wrap_add(
  gravity_vm* vm,
  gravity_value_t* args, uint32_t nargs, 
  uint32_t retIndex, void* data
) {
  int lhs, rhs, rt;

  // Unwrap
  lhs = VALUE_AS_INT(args[1]);
  rhs = VALUE_AS_INT(args[2]);

  // Perform call, capture return
  rt = add(lhs, rhs);

  // Forward the return
  gravity_vm_setslot(vm, VALUE_FROM_INT(rt), retIndex);
}

By using C++ (98) templating or C preprocessor magic, would there be a way to generate wrapper functions?

A very, very crunched example of the above wrapper function, would be this one:

static void gravity_wrap_add(
  gravity_vm* vm,
  gravity_value_t* args, uint32_t nargs, 
  uint32_t retIndex, void* data
) {
  gravity_vm_setslot(vm, 
   VALUE_FROM_INT(
     add(VALUE_AS_INT(args[1]), VALUE_AS_INT(args[2]))
   ), 
  retIndex);
}

This version is technically what I want to achieve - but through methods like the preprocessor or C++ templating. For cross-platform compatibility reasons, I'd like to stick with C++98 (since MSVC isn't exactly good on modern features).

Kerrek SB
  • 464,522
  • 92
  • 875
  • 1,084
Ingwie Phoenix
  • 2,703
  • 2
  • 24
  • 33
  • Hard to tell exactly what you are attempting, but if I understand, you could include `limits.h` and within `gravity_wrap_add` validate `INT_MIN <= args[1] && args[1] <= INT_MAX` (the same for `args[2]`) and then simply cast `(int)args[1]` and for `args[2]` -- presuming `gravity_value_t` is the same size as `int` on your system. If that's not what you are asking, update your question. (admittedly, I may be completely missing the point as I'm not familiar with gravity) That would be C89/C++98 compiant. – David C. Rankin Jan 29 '19 at 01:47

1 Answers1

0

It’s pretty tedious without variadic templates, but you should be able to do a small number of arguments:

namespace gravity {
  template<class T> struct value {};
  // for brevity and deduction:
  template<class T> T get(const gravity_value_t &v)
  {return value<T>::get(v);}
  template<class T> gravity_value_t put(const T &t)
  {return value<T>::put(t);}
  template<> struct value<int> {
    int get(const gravity_value_t &v)
    {return VALUE_AS_INT(v);}
    gravity_value_t put(int i)
    {return VALUE_FROM_INT(i);}
  };
  // more specializations…

  template<class R,R f()>
  void wrap(gravity_vm* vm,
            gravity_value_t* args, uint32_t nargs, 
            uint32_t retIndex, void* data) {
    assert(nargs==0);
    gravity_vm_setslot(vm,put(f()),retIndex);
  }
  template<class R,class A,R f(A)>
  void wrap(gravity_vm* vm,
            gravity_value_t* args, uint32_t nargs, 
            uint32_t retIndex, void* data) {
    assert(nargs==1);
    gravity_vm_setslot(vm,put(f(get<A>(args[0]))),retIndex);
  }
  // more overloads with more arguments…

  /*gravity_register*/(wrap<int,int,int,add>);
}

As usual, void return types will be a pain; you have to repeat each argument-count overload for R of void.

It would be possible to use deduction to avoid repeating the signature when using wrap, but then you get fewer functions and extra closure objects to distinguish them (which you can presumably use via the data argument).

Davis Herring
  • 36,443
  • 4
  • 48
  • 76