diff --git a/c_bindings/pocketpy_c.cpp b/c_bindings/pocketpy_c.cpp new file mode 100644 index 00000000..2e88fa11 --- /dev/null +++ b/c_bindings/pocketpy_c.cpp @@ -0,0 +1,544 @@ +#include "pocketpy.h" +#include "pocketpy_c.h" + +using namespace pkpy; + +#define PKPY_STACK_SIZE 32 + +#define SAFEGUARD_OPEN try { \ + +#define SAFEGUARD_CLOSE \ + } catch(std::exception& e) { \ + std::cerr << "ERROR: a std::exception " \ + << "this probably means pocketpy itself has a bug!\n" \ + << e.what() << "\n"; \ + exit(2); \ + } catch(...) { \ + std::cerr << "ERROR: a unknown exception was thrown " \ + << "this probably means pocketpy itself has a bug!\n"; \ + exit(2); \ + } + + +#define ERRHANDLER_OPEN SAFEGUARD_OPEN \ + try { \ + if (vm->c_data->size() > 0 && vm->c_data->top() == nullptr) \ + return false; \ + +#define ERRHANDLER_CLOSE \ + } catch( Exception e ) { \ + vm->c_data->push(py_var(vm, e)); \ + vm->c_data->push(NULL); \ + return false; \ + } \ + SAFEGUARD_CLOSE \ + + + +class CVM : public VM { + public : + + ValueStackImpl* c_data; + CVM(bool enable_os=true) : VM(enable_os) { + c_data = new ValueStackImpl(); + } + + ~CVM() { + c_data->clear(); + delete c_data; + } +}; + + +//for now I will unpack a tuple automatically, we may not want to handle +//it this way, not sure +//it is more lua like, but maybe not python like +static void unpack_return(CVM* vm, PyObject* ret) { + if (is_type(ret, vm->tp_tuple)) { + Tuple& t = py_cast(vm, ret); + for (int i = 0; i < t.size(); i++) + vm->c_data->push(t[i]); + } else if (ret == vm->None) { + //do nothing here + //having to pop the stack after every call that returns none is annoying + //lua does not do this + // + //so for now we will not push none on the stack when it is the sole thing returned + //if this becomes a problem we can change it + // + //you can still check if it returned none by comparing stack size before + //and after if you have to + } else + vm->c_data->push(ret); + +} + + +bool pkpy_clear_error(pkpy_vm* vm_handle, char** message) { + CVM* vm = (CVM*) vm_handle; + SAFEGUARD_OPEN + + if (vm->c_data->size() == 0 || vm->c_data->top() != nullptr) + return false; + + vm->c_data->pop(); + Exception& e = py_cast(vm, vm->c_data->top()); + if (message != nullptr) + *message = e.summary().c_str_dup(); + else + std::cerr << "ERROR: " << e.summary() << "\n"; + + vm->c_data->clear(); + vm->callstack.clear(); + vm->s_data.clear(); + return true; + + SAFEGUARD_CLOSE +} + +void gc_marker_ex(CVM* vm) { + for(PyObject* obj: *vm->c_data) if(obj!=nullptr) OBJ_MARK(obj); +} + + +static void noop_output_handler(VM* vm, const Str& str) { + (void) vm; + (void) str; +} + +pkpy_vm* pkpy_vm_create(bool use_stdio, bool enable_os) { + + CVM* vm = new CVM(enable_os); + vm->c_data = new ValueStackImpl(); + vm->_gc_marker_ex = (void (*)(VM*)) gc_marker_ex; + + if (!use_stdio) { + vm->_stdout = noop_output_handler; + vm->_stderr = noop_output_handler; + } + + return (pkpy_vm*) vm; +} + +bool pkpy_vm_run(pkpy_vm* vm_handle, const char* source) { + CVM* vm = (CVM*) vm_handle; + ERRHANDLER_OPEN + + CodeObject_ code = vm->compile(source, "", EXEC_MODE); + PyObject* result = vm->_exec(code, vm->_main); + + //unpack_return(w, result); + //NOTE: it seems like vm->_exec should return whatever the last command it + //ran returned but instead it seems to pretty much always return None + //so I guess uncomment this line if that every changes + + return true; + ERRHANDLER_CLOSE +} + +void pkpy_vm_destroy(pkpy_vm* vm_handle) { + CVM* vm = (CVM*) vm_handle; + delete vm; +} + +static void propagate_if_errored(CVM* vm, ValueStackImpl* stored_stack) { + try { + if (vm->c_data->size() == 0 || vm->c_data->top() != nullptr) + return; + + vm->c_data->pop(); + Exception& e = py_cast(vm, vm->c_data->top()); + vm->c_data->pop(); + + vm->c_data = stored_stack; + + throw e; + } catch(Exception& e) { + throw; + } catch(...) { + std::cerr << "ERROR: a non pocketpy exeception was thrown " + << "this probably means pocketpy itself has a bug!\n"; + exit(2); + } +} + +PyObject* c_function_wrapper(VM* vm, ArgsView args) { + LuaStyleFuncC f = py_cast(vm, args[-2])._lua_f; + CVM* cvm = (CVM*) vm; + + //setup c stack + ValueStackImpl local_stack; + + for (int i = 0; i < args.size(); i++) + local_stack.push(args[i]); + + ValueStackImpl* stored_stack = cvm->c_data; + cvm->c_data = &local_stack; + + int retc = f(cvm); + + propagate_if_errored(cvm, stored_stack); + cvm->c_data = stored_stack; + + PyObject* ret = cvm->None; + + if (retc == 1) + ret = local_stack.top(); + else if (retc > 1) { + Tuple t(retc); + + for (int i = 0; i < retc; i++) { + int stack_index = (local_stack.size() - retc) + i; + t[i] = local_stack.begin()[stack_index]; + } + + ret = py_var(cvm, t); + } + + return ret; +} + +bool pkpy_push_function(pkpy_vm* vm_handle, pkpy_function f) { + CVM* vm = (CVM*) vm_handle; + ERRHANDLER_OPEN + + //TODO right now we just treat all c bound functions a varargs functions + //do we want to change that? + NativeFunc nf = NativeFunc(c_function_wrapper, -1, 0); + nf._lua_f = (LuaStyleFuncC) f; + + vm->c_data->push(py_var(vm, nf)); + + return true; + ERRHANDLER_CLOSE +} + +bool pkpy_push_int(pkpy_vm* vm_handle, int value) { + CVM* vm = (CVM*) vm_handle; + ERRHANDLER_OPEN + + vm->c_data->push(py_var(vm, value)); + + return true; + ERRHANDLER_CLOSE +} + +bool pkpy_push_float(pkpy_vm* vm_handle, double value) { + CVM* vm = (CVM*) vm_handle; + ERRHANDLER_OPEN + vm->c_data->push(py_var(vm, value)); + + return true; + ERRHANDLER_CLOSE +} + +bool pkpy_push_bool(pkpy_vm* vm_handle, bool value) { + CVM* vm = (CVM*) vm_handle; + ERRHANDLER_OPEN + vm->c_data->push(py_var(vm, value)); + + return true; + ERRHANDLER_CLOSE +} + +bool pkpy_push_string(pkpy_vm* vm_handle, const char* value) { + CVM* vm = (CVM*) vm_handle; + ERRHANDLER_OPEN + vm->c_data->push(py_var(vm, value)); + + return true; + ERRHANDLER_CLOSE +} + +bool pkpy_push_stringn(pkpy_vm* vm_handle, const char* value, int length) { + CVM* vm = (CVM*) vm_handle; + ERRHANDLER_OPEN + + Str s = Str(value, length); + vm->c_data->push(py_var(vm, s)); + + return true; + ERRHANDLER_CLOSE +} + +bool pkpy_push_voidp(pkpy_vm* vm_handle, void* value) { + CVM* vm = (CVM*) vm_handle; + ERRHANDLER_OPEN + vm->c_data->push(py_var(vm, value)); + + return true; + ERRHANDLER_CLOSE +} + +bool pkpy_push_none(pkpy_vm* vm_handle) { + CVM* vm = (CVM*) vm_handle; + ERRHANDLER_OPEN + vm->c_data->push(vm->None); + + return true; + ERRHANDLER_CLOSE +} + + + +bool pkpy_set_global(pkpy_vm* vm_handle, const char* name) { + CVM* vm = (CVM*) vm_handle; + ERRHANDLER_OPEN + + vm->_main->attr().set(name, vm->c_data->top()); + + vm->c_data->pop(); + + return true; + ERRHANDLER_CLOSE +} + +//get global will also get bulitins +bool pkpy_get_global(pkpy_vm* vm_handle, const char* name) { + CVM* vm = (CVM*) vm_handle; + ERRHANDLER_OPEN + + PyObject* o = vm->_main->attr().try_get(name); + if (o == nullptr) { + o = vm->builtins->attr().try_get(name); + if (o == nullptr) + vm->NameError("could not find requested global"); + } + + vm->c_data->push(o); + + return true; + ERRHANDLER_CLOSE +} + + +bool pkpy_call(pkpy_vm* vm_handle, int argc) { + CVM* vm = (CVM*) vm_handle; + ERRHANDLER_OPEN + + int callable_index = vm->c_data->size() - argc - 1; + + PyObject* callable = vm->c_data->begin()[callable_index]; + + vm->s_data.push(callable); + vm->s_data.push(PY_NULL); + + for (int i = 0; i < argc; i++) + vm->s_data.push(vm->c_data->begin()[callable_index + i + 1]); + + PyObject* o = vm->vectorcall(argc); + + vm->c_data->shrink(argc + 1); + + unpack_return(vm, o); + + return true; + ERRHANDLER_CLOSE +} + +bool pkpy_call_method(pkpy_vm* vm_handle, const char* name, int argc) { + CVM* vm = (CVM*) vm_handle; + ERRHANDLER_OPEN + + int self_index = vm->c_data->size() - argc - 1; + PyObject* self = vm->c_data->begin()[self_index]; + + PyObject* callable = vm->get_unbound_method(self, name, &self); + + vm->s_data.push(callable); + vm->s_data.push(self); + + for (int i = 0; i < argc; i++) + vm->s_data.push(vm->c_data->begin()[self_index + i + 1]); + + PyObject* o = vm->vectorcall(argc); + + vm->c_data->shrink(argc + 1); + + unpack_return(vm, o); + + return true; + ERRHANDLER_CLOSE +} + + + +static int lua_to_cstack_index(int index, int size) { + if (index < 0) + index = size + index; + return index; +} + +bool pkpy_to_int(pkpy_vm* vm_handle, int index, int* ret) { + CVM* vm = (CVM*) vm_handle; + ERRHANDLER_OPEN + + index = lua_to_cstack_index(index, vm->c_data->size()); + + PyObject* o = vm->c_data->begin()[index]; + if (ret != nullptr) + *ret = py_cast(vm, o); + + return true; + ERRHANDLER_CLOSE +} + +bool pkpy_to_float(pkpy_vm* vm_handle, int index, double* ret) { + CVM* vm = (CVM*) vm_handle; + ERRHANDLER_OPEN + + index = lua_to_cstack_index(index, vm->c_data->size()); + + PyObject* o = vm->c_data->begin()[index]; + if (ret != nullptr) + *ret = py_cast(vm, o); + + return true; + ERRHANDLER_CLOSE +} + +bool pkpy_to_bool(pkpy_vm* vm_handle, int index, bool* ret) { + CVM* vm = (CVM*) vm_handle; + ERRHANDLER_OPEN + + index = lua_to_cstack_index(index, vm->c_data->size()); + + PyObject* o = vm->c_data->begin()[index]; + if (ret != nullptr) + *ret = py_cast(vm, o); + + return true; + ERRHANDLER_CLOSE +} + +bool pkpy_to_voidp(pkpy_vm* vm_handle, int index, void** ret) { + CVM* vm = (CVM*) vm_handle; + ERRHANDLER_OPEN + + index = lua_to_cstack_index(index, vm->c_data->size()); + + PyObject* o = vm->c_data->begin()[index]; + if (ret != nullptr) + *ret = py_cast(vm, o); + + return true; + ERRHANDLER_CLOSE +} + +bool pkpy_to_string(pkpy_vm* vm_handle, int index, char** ret) { + CVM* vm = (CVM*) vm_handle; + ERRHANDLER_OPEN + + index = lua_to_cstack_index(index, vm->c_data->size()); + + PyObject* o = vm->c_data->begin()[index]; + if (ret != nullptr) { + Str& s = py_cast(vm, o); + *ret = s.c_str_dup(); + } + + return true; + ERRHANDLER_CLOSE +} + +bool pkpy_to_stringn(pkpy_vm* vm_handle, int index, const char** ret, int* size) { + CVM* vm = (CVM*) vm_handle; + ERRHANDLER_OPEN + + index = lua_to_cstack_index(index, vm->c_data->size()); + + PyObject* o = vm->c_data->begin()[index]; + if (ret != nullptr) { + std::string_view sv = py_cast(vm, o).sv(); + *ret = sv.data(); + *size = sv.size(); + } + + return true; + ERRHANDLER_CLOSE +} + + +bool pkpy_is_int(pkpy_vm* vm_handle, int index) { + CVM* vm = (CVM*) vm_handle; + index = lua_to_cstack_index(index, vm->c_data->size()); + PyObject* o = vm->c_data->begin()[index]; + + return is_type(o, vm->tp_int); +} +bool pkpy_is_float(pkpy_vm* vm_handle, int index) { + CVM* vm = (CVM*) vm_handle; + index = lua_to_cstack_index(index, vm->c_data->size()); + PyObject* o = vm->c_data->begin()[index]; + + return is_type(o, vm->tp_float); +} +bool pkpy_is_bool(pkpy_vm* vm_handle, int index) { + CVM* vm = (CVM*) vm_handle; + index = lua_to_cstack_index(index, vm->c_data->size()); + PyObject* o = vm->c_data->begin()[index]; + + return is_type(o, vm->tp_bool); +} +bool pkpy_is_string(pkpy_vm* vm_handle, int index) { + CVM* vm = (CVM*) vm_handle; + index = lua_to_cstack_index(index, vm->c_data->size()); + PyObject* o = vm->c_data->begin()[index]; + + return is_type(o, vm->tp_str); +} +bool pkpy_is_voidp(pkpy_vm* vm_handle, int index) { + CVM* vm = (CVM*) vm_handle; + index = lua_to_cstack_index(index, vm->c_data->size()); + PyObject* o = vm->c_data->begin()[index]; + + return is_type(o, VoidP::_type(vm)); +} + +bool pkpy_is_none(pkpy_vm* vm_handle, int index) { + CVM* vm = (CVM*) vm_handle; + index = lua_to_cstack_index(index, vm->c_data->size()); + PyObject* o = vm->c_data->begin()[index]; + + return o == vm->None; +} + +bool pkpy_check_global(pkpy_vm* vm_handle, const char* name) { + CVM* vm = (CVM*) vm_handle; + SAFEGUARD_OPEN + PyObject* o = vm->_main->attr().try_get(name); + if (o == nullptr) { + o = vm->builtins->attr().try_get(name); + if (o == nullptr) + return false; + } + return true; + + SAFEGUARD_CLOSE +} + +bool pkpy_check_error(pkpy_vm* vm_handle) { + CVM* vm = (CVM*) vm_handle; + SAFEGUARD_OPEN + if (vm->c_data->size() > 0 && vm->c_data->top() == nullptr) + return true; + return false; + SAFEGUARD_CLOSE +} + + +bool pkpy_check_stack(pkpy_vm* vm_handle, int free) { + CVM* vm = (CVM*) vm_handle; + return free + vm->c_data->size() <= PKPY_STACK_SIZE; +} + +int pkpy_stack_size(pkpy_vm* vm_handle) { + CVM* vm = (CVM*) vm_handle; + return vm->c_data->size(); +} + +bool pkpy_pop(pkpy_vm* vm_handle, int n) { + CVM* vm = (CVM*) vm_handle; + vm->c_data->shrink(n); + return true; +} diff --git a/c_bindings/pocketpy_c.h b/c_bindings/pocketpy_c.h new file mode 100644 index 00000000..7924d75c --- /dev/null +++ b/c_bindings/pocketpy_c.h @@ -0,0 +1,104 @@ +#ifndef POCKETPY_C_H +#define POCKETPY_C_H + +#ifdef __cplusplus +extern "C" { +#endif + +#include +#include + +typedef struct pkpy_vm_handle pkpy_vm; + +//we we take a lot of inspiration from the lua api for these bindings +//the key difference being most methods return a bool, +//true if it succeeded false if it did not + +//if a method returns false call the pkpy_clear_error method to check the error and clear it +//if pkpy_clear_error returns false it means that no error was set, and it takes no action +//if pkpy_clear_error returns true it means there was an error and it was cleared, +//it will provide a string summary of the error in the message parameter (if it is not NULL) +//if null is passed in as message, and it will just print the message to stderr +bool pkpy_clear_error(pkpy_vm*, char** message); +//NOTE you are responsible for freeing message + +pkpy_vm* pkpy_vm_create(bool use_stdio, bool enable_os); +bool pkpy_vm_run(pkpy_vm*, const char* source); +void pkpy_vm_destroy(pkpy_vm*); + +typedef int (*pkpy_function)(pkpy_vm*); + +bool pkpy_pop(pkpy_vm*, int n); + +bool pkpy_push_function(pkpy_vm*, pkpy_function); +bool pkpy_push_int(pkpy_vm*, int); +bool pkpy_push_float(pkpy_vm*, double); +bool pkpy_push_bool(pkpy_vm*, bool); +bool pkpy_push_string(pkpy_vm*, const char*); +bool pkpy_push_stringn(pkpy_vm*, const char*, int length); +bool pkpy_push_voidp(pkpy_vm*, void*); +bool pkpy_push_none(pkpy_vm*); + +bool pkpy_set_global(pkpy_vm*, const char* name); +bool pkpy_get_global(pkpy_vm*, const char* name); + +//first push callable you want to call +//then push the arguments to send +//argc is the number of arguments that was pushed (not counting the callable) +bool pkpy_call(pkpy_vm*, int argc); + +//first push the object the method belongs to (self) +//then push the the argments +//argc is the number of arguments that was pushed (not counting the callable or self) +//name is the name of the method to call on the object +bool pkpy_call_method(pkpy_vm*, const char* name, int argc); + + +//we will break with the lua api here +//lua uses 1 as the index to the first pushed element for all of these functions +//but we will start counting at zero to match python +//we will allow negative numbers to count backwards from the top +bool pkpy_to_int(pkpy_vm*, int index, int* ret); +bool pkpy_to_float(pkpy_vm*, int index, double* ret); +bool pkpy_to_bool(pkpy_vm*, int index, bool* ret); +bool pkpy_to_voidp(pkpy_vm*, int index, void** ret); + +//this method provides a strong reference, you are responsible for freeing the +//string when you are done with it +bool pkpy_to_string(pkpy_vm*, int index, char** ret); + +//this method provides a weak reference, it is only valid until the +//next api call +//it is not null terminated +bool pkpy_to_stringn(pkpy_vm*, int index, const char** ret, int* size); + + +//these do not follow the same error semantics as above, their return values +//just say whether the check succeeded or not, or else return the value asked for + +bool pkpy_is_int(pkpy_vm*, int index); +bool pkpy_is_float(pkpy_vm*, int index); +bool pkpy_is_bool(pkpy_vm*, int index); +bool pkpy_is_string(pkpy_vm*, int index); +bool pkpy_is_voidp(pkpy_vm*, int index); +bool pkpy_is_none(pkpy_vm*, int index); + + +//will return true if global exists +bool pkpy_check_global(pkpy_vm*, const char* name); + +//will return true if the vm is currently in an error state +bool pkpy_check_error(pkpy_vm*); + +//will return true if at least free empty slots remain on the stack +bool pkpy_check_stack(pkpy_vm*, int free); + +//returns the number of elements on the stack +int pkpy_stack_size(pkpy_vm*); + + +#ifdef __cplusplus +} +#endif + +#endif diff --git a/c_bindings/test.c b/c_bindings/test.c new file mode 100644 index 00000000..86adf20e --- /dev/null +++ b/c_bindings/test.c @@ -0,0 +1,300 @@ +#include "pocketpy_c.h" +#include +#include + +//tests the c bindings for pocketpy + +void check_impl(pkpy_vm* vm, bool result, int lineno) { + if (!result) { + printf("ERROR: failed where it should have succeed at line %i\n", lineno); + char* message; + if (!pkpy_clear_error(vm, &message)) { + printf("clear error reported everything was fine\n"); + exit(1); + } + + printf("%s\n", message); + free(message); + exit(1); + } +} + +void fail_impl(pkpy_vm* vm, bool result, int lineno) { + if (result) { + printf("ERROR: succeeded where it should have failed line %i\n", lineno); + exit(1); + } else { + char* message; + if (pkpy_clear_error(vm, &message)) { + printf("actually errored!\n"); + free(message); + exit(1); + } + } +} + +void error_impl(pkpy_vm* vm, bool result, int lineno) { + if (result) { + printf("ERROR: succeeded where it should have failed line %i\n", lineno); + exit(1); + } else { + char* message; + if (!pkpy_clear_error(vm, &message)) + printf("clear error reported everything was fine\n"); + else { + printf("successfully errored with this message: \n"); + printf("%s\n", message); + free(message); + } + } +} + + +#define check(r) check_impl(vm, (r), __LINE__) +#define fail(r) fail_impl(vm, (r), __LINE__) +#define error(r) error_impl(vm, (r), __LINE__) + +int test_binding(pkpy_vm* vm) { + pkpy_push_int(vm, 12); + return 1; +} + +int test_multiple_return(pkpy_vm* vm) { + pkpy_push_int(vm, 12); + pkpy_push_int(vm, 13); + return 2; +} + +int test_return_none(pkpy_vm* vm) { + return 0; +} + +int test_error_propagate(pkpy_vm* vm) { + pkpy_get_global(vm, "does not exist"); + return 1; +} + + +pkpy_vm* vm; + +void cleanup(void) { + pkpy_vm_destroy(vm); +} + +int main(int argc, char** argv) { + + vm = pkpy_vm_create(true, true); + atexit(cleanup); + + //test run + check(pkpy_vm_run(vm, "print('hello world!')")); + + error(pkpy_get_global(vm, "nonexistatn")); + + printf("\ntesting int methods\n"); + int r_int; + check(pkpy_push_int(vm, 11)); + check(pkpy_set_global(vm, "eleven")); + check(pkpy_vm_run(vm, "print(eleven)")); + check(pkpy_get_global(vm, "eleven")); + check(pkpy_is_int(vm, -1)); + check(pkpy_to_int(vm, -1, &r_int)); + printf("%i\n", r_int); + fail(pkpy_is_float(vm, -1)); + fail(pkpy_is_bool(vm, -1)); + fail(pkpy_is_string(vm, -1)); + fail(pkpy_is_none(vm, -1)); + fail(pkpy_is_voidp(vm, -1)); + + printf("\ntesting float methods\n"); + double r_float; + check(pkpy_push_float(vm, 11.11)); + check(pkpy_set_global(vm, "elevenf")); + check(pkpy_vm_run(vm, "print(elevenf)")); + check(pkpy_get_global(vm, "elevenf")); + check(pkpy_is_float(vm, -1)); + check(pkpy_to_float(vm, -1, &r_float)); + printf("%f\n", r_float); + fail(pkpy_is_int(vm, -1)); + fail(pkpy_is_bool(vm, -1)); + fail(pkpy_is_string(vm, -1)); + fail(pkpy_is_none(vm, -1)); + fail(pkpy_is_voidp(vm, -1)); + + printf("\ntesting bool methods\n"); + bool r_bool; + check(pkpy_push_bool(vm, false)); + check(pkpy_set_global(vm, "false_test")); + check(pkpy_vm_run(vm, "print(false_test)")); + check(pkpy_get_global(vm, "false_test")); + check(pkpy_is_bool(vm, -1)); + check(pkpy_to_bool(vm, -1, &r_bool)); + printf("%i\n", r_bool); + fail(pkpy_is_int(vm, -1)); + fail(pkpy_is_float(vm, -1)); + fail(pkpy_is_string(vm, -1)); + fail(pkpy_is_none(vm, -1)); + fail(pkpy_is_voidp(vm, -1)); + + printf("\ntesting string methods\n"); + char* r_string; + check(pkpy_push_string(vm, "hello!")); + check(pkpy_set_global(vm, "hello1")); + check(pkpy_vm_run(vm, "print(hello1)")); + check(pkpy_push_stringn(vm, "hello!", 5)); + check(pkpy_is_string(vm, -1)); + check(pkpy_to_string(vm, -1, &r_string)); + printf("%s\n", r_string); + free(r_string); + const char* r_stringn; + int r_size; + check(pkpy_to_stringn(vm, -1, &r_stringn, &r_size)); + printf("%.*s\n", r_size, r_stringn); + fail(pkpy_is_int(vm, -1)); + fail(pkpy_is_float(vm, -1)); + fail(pkpy_is_bool(vm, -1)); + fail(pkpy_is_none(vm, -1)); + fail(pkpy_is_voidp(vm, -1)); + + printf("\ntesting None methods\n"); + check(pkpy_push_none(vm)); + check(pkpy_set_global(vm, "none")); + check(pkpy_vm_run(vm, "print(none)")); + check(pkpy_get_global(vm, "none")); + check(pkpy_is_none(vm, -1)); + fail(pkpy_is_int(vm, -1)); + fail(pkpy_is_float(vm, -1)); + fail(pkpy_is_bool(vm, -1)); + fail(pkpy_is_string(vm, -1)); + fail(pkpy_is_voidp(vm, -1)); + + printf("\ntesting voidp methods\n"); + void* vp = (void*) 123; + check(pkpy_push_voidp(vm, vp)); + check(pkpy_set_global(vm, "vp")); + check(pkpy_vm_run(vm, "print(vp)")); + check(pkpy_get_global(vm, "vp")); + check(pkpy_is_voidp(vm, -1)); + vp = NULL; + check(pkpy_to_voidp(vm, -1, &vp)); + printf("%i\n", (int) (intptr_t) vp); + fail(pkpy_is_int(vm, -1)); + fail(pkpy_is_float(vm, -1)); + fail(pkpy_is_bool(vm, -1)); + fail(pkpy_is_string(vm, -1)); + fail(pkpy_is_none(vm, -1)); + + + printf("\ntesting sizing and indexing\n"); + int stack_size = pkpy_stack_size(vm); + printf("stack size %i\n", stack_size); + check(pkpy_check_stack(vm, 10)); + check(pkpy_check_stack(vm, 26)); + fail(pkpy_check_stack(vm, 27)); + check(pkpy_is_int(vm, 0)); + check(pkpy_is_float(vm, 1)); + check(pkpy_is_bool(vm, 2)); + check(pkpy_is_string(vm, 3)); + check(pkpy_is_none(vm, 4)); + check(pkpy_is_voidp(vm, 5)); + check(pkpy_is_int(vm, -6)); + check(pkpy_is_float(vm, -5)); + check(pkpy_is_bool(vm, -4)); + check(pkpy_is_string(vm, -3)); + check(pkpy_is_none(vm, -2)); + check(pkpy_is_voidp(vm, -1)); + + printf("\ntesting error catching\n"); + error(pkpy_vm_run(vm, "let's make sure syntax errors get caught")); + check(pkpy_stack_size(vm) == 0); //stack should be cleared after error is resolved + + printf("\ntesting calls\n"); + + check(pkpy_vm_run(vm, "def x(x, y) : return x - y")); + check(pkpy_vm_run(vm, "def vararg_x(*x) : return sum(x)")); + check(pkpy_vm_run(vm, "def keyword_x(x=1, y=1) : return x+y")); + check(pkpy_vm_run(vm, "def retmany_x() : return 1, 2, 3")); + + check(pkpy_get_global(vm, "x")); + check(pkpy_push_int(vm, 2)); + check(pkpy_push_int(vm, 3)); + check(pkpy_call(vm, 2)); + check(pkpy_to_int(vm, -1, &r_int)); + printf("x : %i\n", r_int); + + check(pkpy_get_global(vm, "vararg_x")); + check(pkpy_push_int(vm, 1)); + check(pkpy_push_int(vm, 2)); + check(pkpy_push_int(vm, 3)); + check(pkpy_push_int(vm, 4)); + check(pkpy_push_int(vm, 5)); + check(pkpy_push_int(vm, 6)); + check(pkpy_call(vm, 6)); + check(pkpy_to_int(vm, -1, &r_int)); + printf("vararg_x : %i\n", r_int); + + check(pkpy_get_global(vm, "keyword_x")); + check(pkpy_push_int(vm, 3)); + check(pkpy_call(vm, 1)); + check(pkpy_to_int(vm, -1, &r_int)); + printf("keyword_x : %i\n", r_int); + + check(pkpy_get_global(vm, "keyword_x")); + check(pkpy_call(vm, 0)); + check(pkpy_to_int(vm, -1, &r_int)); + printf("keyword_x : %i\n", r_int); + + check(pkpy_stack_size(vm) == 4); + + check(pkpy_get_global(vm, "retmany_x")); + check(pkpy_call(vm, 0)); + check(pkpy_stack_size(vm) == 7); + check(pkpy_to_int(vm, -3, &r_int)); + printf("retmany_x : %i\n", r_int); + check(pkpy_to_int(vm, -2, &r_int)); + printf("retmany_x : %i\n", r_int); + check(pkpy_to_int(vm, -1, &r_int)); + printf("retmany_x : %i\n", r_int); + + check(pkpy_get_global(vm, "x")); + error(pkpy_call(vm, 0)); + + check(pkpy_vm_run(vm, "l = []")); + + check(pkpy_get_global(vm, "l")); + check(pkpy_push_string(vm, "hello")); + check(pkpy_call_method(vm, "append", 1)); + check(pkpy_vm_run(vm, "print(l)")); + + + printf("\ntesting pushing functions\n"); + + check(pkpy_push_function(vm, test_binding)); + check(pkpy_set_global(vm, "test_binding")); + check(pkpy_vm_run(vm, "print(test_binding())")); + + check(pkpy_push_function(vm, test_multiple_return)); + check(pkpy_set_global(vm, "test_multiple_return")); + + //uncomment if _exec changes + //check(pkpy_vm_run(vm, "test_multiple_return()")); + //check(pkpy_stack_size(vm) == 2); + + check(pkpy_push_function(vm, test_error_propagate)); + check(pkpy_set_global(vm, "test_error_propagate")); + error(pkpy_vm_run(vm, "test_error_propagate()")); + + check(pkpy_get_global(vm, "test_multiple_return")); + check(pkpy_call(vm, 0)); + check(pkpy_stack_size(vm) == 2); + + check(pkpy_pop(vm, 2)); + check(pkpy_stack_size(vm) == 0); + + check(pkpy_check_global(vm, "test_error_propagate")); + fail(pkpy_check_global(vm, "nonexistant")); + pkpy_vm_run(vm, "test_error_propagate()"); + check(pkpy_check_error(vm)); + + return 0; +} diff --git a/c_bindings/test_answers.txt b/c_bindings/test_answers.txt new file mode 100644 index 00000000..19d195d5 --- /dev/null +++ b/c_bindings/test_answers.txt @@ -0,0 +1,51 @@ +hello world! +successfully errored with this message: +NameError: name 'could not find requested global' is not defined + +testing int methods +11 +11 + +testing float methods +11.11 +11.110000 + +testing bool methods +False +0 + +testing string methods +hello! +hello +hello + +testing None methods +None + +testing voidp methods + +123 + +testing sizing and indexing +stack size 6 + +testing error catching +successfully errored with this message: + File "", line 1 + let's make sure syntax errors get caught +SyntaxError: EOL while scanning string literal + +testing calls +x : -1 +vararg_x : 21 +keyword_x : 4 +keyword_x : 2 +retmany_x : 1 +retmany_x : 2 +retmany_x : 3 +successfully errored with this message: +TypeError: expected 2 positional arguments, but got 0 (x) +['hello'] + +testing pushing functions +12 diff --git a/run_c_binding_test.sh b/run_c_binding_test.sh new file mode 100644 index 00000000..172e1660 --- /dev/null +++ b/run_c_binding_test.sh @@ -0,0 +1,26 @@ +python3 preprocess.py + +if [ ! -f "pocketpy_c.o" ] +then + echo "compiling c++ lib" + clang++ -c -o pocketpy_c.o c_bindings/pocketpy_c.cpp -Wfatal-errors --std=c++17 -O2 -Wall -Wno-sign-compare -Wno-unused-variable -fno-rtti -stdlib=libc++ -I src/ -fsanitize=address -g +else + echo "DETECTED PREVIOUS COMPILATION USING IT" +fi + +echo "compiling c executable" +clang -c -o test.o c_bindings/test.c -Wfatal-errors -O2 -Wall -Wno-sign-compare -Wno-unused-variable -I src/ -fsanitize=address -g +echo "linking" +clang++ -o c_binding_test test.o pocketpy_c.o -stdlib=libc++ -fsanitize=address -g +echo "running, leaksanitizer is finding a false postive leak in the CVM constructor" +echo "ignore that but pay attention to anything else" +./c_binding_test > binding_test_scratch +echo "checking results (they should be identical)" +diff -q -s binding_test_scratch c_bindings/test_answers.txt + +echo "cleaning up" +rm pocketpy_c.o +rm test.o +rm binding_test_scratch +rm c_binding_test +