Last active
December 25, 2018 20:02
-
-
Save IngwiePhoenix/662bfd2f78914157b9eb01d96870c0ac to your computer and use it in GitHub Desktop.
gravity c api - oop
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
/** | |
* @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