Skip to content

Instantly share code, notes, and snippets.

@IngwiePhoenix
Last active December 25, 2018 20:02
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save IngwiePhoenix/662bfd2f78914157b9eb01d96870c0ac to your computer and use it in GitHub Desktop.
Save IngwiePhoenix/662bfd2f78914157b9eb01d96870c0ac to your computer and use it in GitHub Desktop.
gravity c api - oop
/**
* @file
* Demonstrating how to use classes or opaque data types
* within Gravity.
*
* This does NOT use xData attached to functions...
* but rather gravity_instance_t's xdata.
*/
#include <stdio.h>
#include <string.h>
#include <string> // std::string
#include "gravity_compiler.h"
#include "gravity_macros.h"
#include "gravity_vm.h"
#include "gravity_core.h"
#include "gravity_value.h"
// Gravity has some nice value dumping capabilities.
// This macro "abuses" that. ;)
#define DUMP(_val) { \
char buffer[512]; \
gravity_value_dump(vm, _val, buffer, sizeof(buffer)); \
printf(#_val ": %s\n", buffer); \
}
// Convenience macro for value conversion
inline ::std::string VALUE_AS_CXXSTRING(gravity_value_t v) {
gravity_string_t *str = VALUE_AS_STRING(v);
return ::std::string(str->s, str->len);
}
// I will demonstrate both - a class and an opaque type.
// For the opaque type, I will use FILE*.
// With opaque type, I basically mean the C-style "object" methods.
// I.e.:
// - A struct called Foo.
// - A function called foo_create()
// - A function called foo_free(Foo*)
// - A setter called foo_set_value(Foo*, char*)
// - A getter called foo_get_value(Foo*);
// ...and so on and so forth.
/**
* @section C++ OOP
* This class contains a constructor, a destructor and a method. The
* constructor consumes one argument and stores that internally. Then, the
* single instance method uses that to print a message.
*
* Beneath are a few static methods to help bridging the functions across and
* managing type conversions. Thus, a mass-registration function is used, too.
*
* You can try adding a new method to this class, and reference it within the
* registerClass()'s inner struct. Then write a wrapper method ontop of it.
* You can now access it. There might be a few ways to automate this.
*/
class CXXClass {
private:
std::string *name;
public:
CXXClass(std::string &name) {
// For this purpose, we'll copy the string.
// So we can have something to destruct later.
printf("[debug] CXXClass constructor\n");
this->name = new std::string(name);
}
~CXXClass() {
printf("[debug] CXXClass destructor\n");
delete this->name;
}
void sayIt() {
printf("[debug] CXXClass::sayIt()\n");
printf("Hello, %s!\n", this->name->c_str());
}
// Those are methods for Gravity.
// We declare them static for easier access.
// But having a handy helper macro can be useful...
#define GRAVITY_SIG gravity_vm *vm, gravity_value_t *argv, uint16_t argc, uint32_t rindex
static bool CXXClass_init(GRAVITY_SIG) {
DUMP(argv[0]);
DUMP(argv[1]);
printf("#argv: %i\n", argc);
// sanity check: arguments
if(argc < 2 || !VALUE_ISA_STRING(argv[1])) {
// argc == 1, which means that no argument was passed.
// We may want to set an error here...
return false;
}
// Since this is a constructor call, argv[0] now holds our instance.
gravity_instance_t *self = VALUE_AS_INSTANCE(argv[0]);
// Fetch the first argument as a string
gravity_string_t *name_val = VALUE_AS_STRING(argv[1]);
//printf("name: (%i) %s\n", name_val->len, name_val->s);
//char *name = name_val->s; // <-- crashes with SIGSEV.
std::string name_str = VALUE_AS_CXXSTRING(argv[1]);
CXXClass *nativeSelf = new CXXClass(name_str);
// You could do some additionnal stuff here with nativeSelf...
gravity_instance_setxdata(self, nativeSelf);
// Turns out that you _have_ to return the instance. Weird...
gravity_vm_setslot(vm, VALUE_FROM_OBJECT(self), rindex);
return true;
}
static bool CXXClass_deinit(GRAVITY_SIG) {
gravity_instance_t *self = VALUE_AS_INSTANCE(argv[0]);
// So there is gravity_instance_setxdata but no get-variant? Okay, I guess.
CXXClass* nativeSelf = (CXXClass*)self->xdata;
// Properly free and destroy.
self->xdata = NULL;
//nativeSelf->~CXXClass(); // calls destructor.
delete nativeSelf; // I think this is actually the way to go. Not sure tho.
return true;
}
static bool CXXClass_sayIt(GRAVITY_SIG) {
gravity_instance_t *self = VALUE_AS_INSTANCE(argv[0]);
CXXClass* nativeSelf = (CXXClass*)self->xdata;
nativeSelf->sayIt();
// The above in one line:
//()(CXXClass*)(VALUE_AS_INSTANCE(argv[0])->xdata))->sayIt();
// Verbosity at it's finest. Consider a macro like this:
//#define VALUE_AS_XDATA(_v, _t) ( (_t) (VALUE_AS_OBJECT(_V) )->xdata )
gravity_vm_setslot(vm, VALUE_FROM_NULL, rindex);
return true;
}
static void registerClass(gravity_vm *vm) {
// A convenience structure. You...probably could copy this, but you
// probably shouldn't copy it into a function, but rather into a global
// namespace or something.
struct funclist_s {
const char* name;
gravity_c_internal func;
uint16_t nparams;
};
funclist_s funcs[] = {
{"init", &CXXClass_init, 1},
{"deinit", &CXXClass_deinit, 0},
{"sayIt", &CXXClass_sayIt, 0}
};
int funcs_c = (int)(sizeof(funcs)/sizeof(funclist_s));
gravity_class_t *c = gravity_class_new_pair(vm, "CXXTest", NULL, 0, 0);
//printf("[debug] Initializing %d functions...\n", funcs_c);
for(int i=0; i<funcs_c; i++) {
gravity_function_t *fn;
gravity_closure_t *fnc;
fn = gravity_function_new_internal(
vm,
funcs[i].name,
funcs[i].func,
funcs[i].nparams
);
fnc = gravity_closure_new(vm, fn);
gravity_class_bind(c, funcs[i].name, VALUE_FROM_OBJECT(fnc));
}
// Finally, register the class.
gravity_vm_setvalue(vm, "CXXClass", VALUE_FROM_OBJECT(c));
}
// PROTIP.
// When your methods are all within the same class... why not use a private
// constructor-like method? ;)
private:
static CXXClass* getThis(gravity_value_t arg0) {
return ((CXXClass*)(VALUE_AS_INSTANCE(arg0))->xdata);
}
// And take this method for an example.
static bool CXXClass_otherMethod(GRAVITY_SIG) {
CXXClass *self = getThis(argv[0]);
// Thats it - you could call your method now.
//self->otherMethod();
return true;
}
};
/**
* @section C-style OOP example
* This is leant against FILE*, but in a much simpler, more basic way.
* It's just for demonstration, after all.
*/
extern "C" {
// Forward decls
struct something_s {
int aNumber;
};
typedef struct something_s SMTH;
SMTH* smth_create();
void smth_free(SMTH*);
bool smth_set_value(SMTH*, int);
void smth_print_self(SMTH*);
// Implementations
SMTH* smth_create() {
SMTH* rt = (SMTH*)malloc(sizeof(SMTH*));
return rt;
}
void smth_free(SMTH* me) {
free(me);
}
bool smth_set_value(SMTH* me, int n) {
// Just imagine this to be "way more complex" :p
me->aNumber = n;
return true;
}
void smth_print_self(SMTH* me) {
printf("[SMTH] {value: %i}\n", me->aNumber);
}
// Now, let's wrap those for Gravity.
bool gravity_smth_init(GRAVITY_SIG) {
gravity_instance_t *self = VALUE_AS_INSTANCE(argv[0]);
SMTH* handle = smth_create();
smth_set_value(handle, 0);
gravity_instance_setxdata(self, handle);
gravity_vm_setslot(vm, VALUE_FROM_OBJECT(self), rindex);
return true;
}
bool gravity_smth_deinit(GRAVITY_SIG) {
gravity_instance_t *self = VALUE_AS_INSTANCE(argv[0]);
SMTH* handle = (SMTH*)self->xdata;
self->xdata = NULL;
smth_free(handle);
return true;
}
bool gravity_smth_set_value(GRAVITY_SIG) {
if(argc < 2 || !VALUE_ISA_INT(argv[1])) {
return false;
}
gravity_instance_t *self = VALUE_AS_INSTANCE(argv[0]);
SMTH* handle = (SMTH*)self->xdata;
int num = (int)VALUE_AS_INT(argv[1]);
bool rt = smth_set_value(handle, num);
gravity_vm_setslot(vm, VALUE_FROM_BOOL(rt), rindex);
return true;
}
bool gravity_smth_print_self(GRAVITY_SIG) {
gravity_instance_t *self = VALUE_AS_INSTANCE(argv[0]);
smth_print_self((SMTH*)self->xdata);
gravity_vm_setslot(vm, VALUE_FROM_NULL, rindex);
return true;
}
void gravity_register_smth(gravity_vm *vm) {
gravity_class_t *c = gravity_class_new_pair(vm, "SMTH_Handle", NULL, 0, 0);
// I am not going to write this so many times now...
#define BIND(_id, _fn, _np) { \
gravity_closure_t *fnc = gravity_closure_new( \
vm, \
gravity_function_new_internal(vm, #_id, _fn, _np) \
); \
gravity_class_bind(c, #_id, VALUE_FROM_OBJECT(fnc)); \
}
BIND(init, gravity_smth_init, 0);
BIND(deinit, gravity_smth_deinit, 0);
BIND(setValue, gravity_smth_set_value, 1);
BIND(print, gravity_smth_print_self, 0);
#undef BIND
// Finally, register the class.
gravity_vm_setvalue(vm, "SMTH_Handle", VALUE_FROM_OBJECT(c));
}
}
// error callback
static void report_error (gravity_vm *vm, error_type_t error_type, const char *message,
error_desc_t error_desc, void *xdata) {
#pragma unused(vm, xdata)
const char *type = "N/A";
switch (error_type) {
case GRAVITY_ERROR_NONE: type = "NONE"; break;
case GRAVITY_ERROR_SYNTAX: type = "SYNTAX"; break;
case GRAVITY_ERROR_SEMANTIC: type = "SEMANTIC"; break;
case GRAVITY_ERROR_RUNTIME: type = "RUNTIME"; break;
case GRAVITY_WARNING: type = "WARNING"; break;
case GRAVITY_ERROR_IO: type = "I/O"; break;
}
if (error_type == GRAVITY_ERROR_RUNTIME) printf("RUNTIME ERROR: ");
else printf("%s ERROR on %d (%d,%d): ", type, error_desc.fileid, error_desc.lineno, error_desc.colno);
printf("%s\n", message);
}
// Example source to execute.
const char* source =
"extern var CXXClass;\n"
"extern var SMTH_Handle;\n"
"func main() {\n"
" var inst = CXXClass(\"Ingwie\");\n"
" inst.sayIt();\n"
" var h = SMTH_Handle();\n"
" h.print();\n"
" h.setValue(15);\n"
" h.print();\n"
"}";
int main(int argc, const char * argv[]) {
gravity_delegate_t delegate = {
.error_callback = report_error
};
gravity_compiler_t *compiler = gravity_compiler_create(&delegate);
gravity_closure_t *closure = gravity_compiler_run(compiler, source, strlen(source), 0, true, false);
if (!closure) return -1;
gravity_vm *vm = gravity_vm_new(&delegate);
gravity_compiler_transfer(compiler, vm);
gravity_compiler_free(compiler);
// Register
CXXClass::registerClass(vm);
gravity_register_smth(vm);
if (gravity_vm_runmain(vm, closure)) {
gravity_value_t result = gravity_vm_result(vm);
DUMP(result);
}
if (vm) gravity_vm_free(vm);
gravity_core_free();
}
/*
Ingwie@Ingwies-Macbook-Pro.local ~/W/G/g/out $ g++ -o classtest classtest.cpp libgravity.a -I ../src/shared/ -I ../src/compiler/ -I ../src/runtime/ -I ../src/utils/; and ./classtest
argv[0]: (INSTANCE) CXXTest (0x7ffd03c17c70)
argv[1]: (STRING) Ingwie (0x7ffd03c1a650)
#argv: 2
[debug] CXXClass constructor
[debug] CXXClass::sayIt()
Hello, Ingwie!
[SMTH] {value: 0}
[SMTH] {value: 15}
result: NULL
*/
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment