Просмотр исходного кода

finalized initial c api and built tests for it

Kolten Pearson 2 лет назад
Родитель
Сommit
d4b9d354ed
7 измененных файлов с 660 добавлено и 98 удалено
  1. 0 41
      c_bindings/main.c
  2. 278 42
      c_bindings/pocketpy_c.cpp
  3. 46 15
      c_bindings/pocketpy_c.h
  4. 262 0
      c_bindings/test.c
  5. 49 0
      c_bindings/test_answers.txt
  6. 24 0
      run_c_binding_test.sh
  7. 1 0
      src/frame.h

+ 0 - 41
c_bindings/main.c

@@ -1,41 +0,0 @@
-#include "pocketpy_c.h"
-#include <stdio.h>
-
-//tests the c bindings for pocketpy
-
-
-int test_binding(pkpy_vm vm) {
-    pkpy_push_int(vm, 12);
-    return 1;
-}
-
-int main(int argc, char** argv) {
-
-    pkpy_vm vm = pkpy_vm_create(true, true);
-
-    pkpy_vm_exec(vm, "print('hello world!')");
-
-    pkpy_push_int(vm, 11);
-    pkpy_set_global(vm, "eleven");
-
-    pkpy_push_function(vm, test_binding);
-    pkpy_set_global(vm, "binding");
-
-    pkpy_vm_exec(vm, "print(eleven)");
-    pkpy_vm_exec(vm, "print(binding())");
-
-    pkpy_vm_exec(vm, "def x(x) : return x + 1");
-
-    pkpy_get_global(vm, "x");
-    pkpy_push_int(vm, 1);
-    pkpy_call(vm, 1);
-
-    int r;
-    pkpy_to_int(vm, -1, &r);
-    printf("%i\n", r);
-
-    pkpy_clear_error(vm, NULL);
-
-    pkpy_vm_destroy(vm);
-    return 0;
-}

+ 278 - 42
c_bindings/pocketpy_c.cpp

@@ -3,52 +3,82 @@
 
 using namespace pkpy;
 
+#define SAFEGUARD_OPEN try { \
 
-#define ERRHANDLER_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.top() == nullptr) \
         return false; \
 
 #define ERRHANDLER_CLOSE \
     } catch( Exception e ) { \
-        vm->c_data.clear(); \
-        vm->c_data.push(VAR(e.summary())); \
+        vm->c_data.push(VAR(e)); \
         vm->c_data.push(NULL); \
         return false; \
     } \
-    } catch(...) { \
-        std::cerr << "ERROR: a non pocketpy exeception was thrown " \
-        << "this probably means pocketpy itself has a bug!\n"; \
-        exit(2); \
-    }
+    SAFEGUARD_CLOSE \
+
+
+//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(VM* vm, PyObject* ret) {
+    if (is_type(ret, vm->tp_tuple)) {
+        Tuple& t = CAST(Tuple&, 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 too
+    } else 
+        vm->c_data.push(ret);
 
-bool pkpy_clear_error(pkpy_vm vm_handle, const char** message) {
+}
+
+
+bool pkpy_clear_error(pkpy_vm vm_handle, char** message) {
     VM* vm = (VM*) vm_handle;
+    SAFEGUARD_OPEN
 
-    try {
         if (vm->c_data.top() != nullptr) 
             return false;
 
         vm->c_data.pop();
-        Str wrapped_message = CAST(Str&, vm->c_data.top());
+        Exception& e = CAST(Exception&, vm->c_data.top());
         if (message != nullptr) 
-            *message = wrapped_message.c_str_dup();
+            *message = e.summary().c_str_dup();
         else
-            std::cerr << "ERROR: " << wrapped_message << "\n";
-
-        vm->c_data.pop();
-        //at this point the stack is clear
+            std::cerr << "ERROR: " << e.summary() << "\n";
 
+        vm->c_data.clear();
+        vm->callstack.clear();
+        vm->s_data.clear(); 
         return true;
 
-    } catch(...) {
-        std::cerr << "ERROR: a non pocketpy exeception was thrown " 
-        << "this probably means pocketpy itself has a bug!\n"; 
-        exit(2); 
-    }
+    SAFEGUARD_CLOSE
 }
 
-pkpy_vm pkpy_vm_create(bool use_stdio, bool enable_os) {
+pkpy_vm pkpy_new_vm(bool use_stdio, bool enable_os) {
     VM* vm = new VM(use_stdio, enable_os);
 
     return (pkpy_vm) vm;
@@ -58,17 +88,55 @@ bool pkpy_vm_exec(pkpy_vm vm_handle, const char* source) {
     VM* vm = (VM*) vm_handle;
     ERRHANDLER_OPEN
 
-    vm->exec(source, "main.py", EXEC_MODE);
+    CodeObject_ code = vm->compile(source, "<c-bound>", EXEC_MODE);
+    PyObject* result = vm->_exec(code, vm->_main);
+    unpack_return(vm, result);
 
     return true;
     ERRHANDLER_CLOSE
 }
 
-void pkpy_vm_destroy(pkpy_vm vm_handle) {
+void pkpy_delete_vm(pkpy_vm vm_handle) {
     VM* vm = (VM*) vm_handle;
     delete vm;
 }
 
+pkpy_repl pkpy_new_repl(pkpy_vm vm_handle) {
+    VM* vm = (VM*) vm_handle;
+    REPL* repl = new REPL(vm);
+    return (pkpy_repl) repl;
+}
+
+bool pkpy_repl_input(pkpy_repl repl_handle, const char* line) {
+    REPL* repl = (REPL*) repl_handle;
+    return repl->input(line);
+}
+
+void pkpy_delete_repl(pkpy_repl repl_handle) {
+    REPL* repl = (REPL*) repl_handle;
+    delete repl;
+}
+
+static void propagate_if_errored(VM* vm) {
+    try {
+        if (vm->c_data.top() != nullptr) 
+            return;
+
+        vm->c_data.pop();
+        Exception& e = CAST(Exception&, vm->c_data.top());
+        vm->c_data.pop();
+
+        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 = CAST(NativeFunc&, args[-2])._lua_f;
 
@@ -81,10 +149,20 @@ PyObject* c_function_wrapper(VM* vm, ArgsView args) {
     int retc = f(vm);
 
     PyObject* ret = vm->None;
+    propagate_if_errored(vm);
 
-    //TODO handle tuple packing for multiple returns
-    if (retc > 0) 
+    if (retc == 1) 
         ret = vm->c_data.top();
+    else if (retc > 1) {
+        Tuple t = Tuple(retc);
+
+        for (int i = 0; i < retc; i++)  {
+            int stack_index = (vm->c_data.size() - retc) + i;
+            t[i] = vm->c_data.get(stack_index);
+        }
+
+        ret = VAR(t);
+    }
 
     vm->c_data.clear();
     vm->c_data.restore(stored);
@@ -127,6 +205,50 @@ bool pkpy_push_float(pkpy_vm vm_handle, double value) {
     ERRHANDLER_CLOSE
 }
 
+bool pkpy_push_bool(pkpy_vm vm_handle, bool value) {
+    VM* vm = (VM*) vm_handle;
+
+    ERRHANDLER_OPEN
+    vm->c_data.push(VAR(value));
+
+    return true;
+    ERRHANDLER_CLOSE
+}
+
+bool pkpy_push_string(pkpy_vm vm_handle, const char* value) {
+    VM* vm = (VM*) vm_handle;
+
+    ERRHANDLER_OPEN
+    vm->c_data.push(VAR(value));
+
+    return true;
+    ERRHANDLER_CLOSE
+}
+
+bool pkpy_push_stringn(pkpy_vm vm_handle, const char* value, int length) {
+    VM* vm = (VM*) vm_handle;
+
+    ERRHANDLER_OPEN
+
+    Str s = Str(value, length);
+    vm->c_data.push(VAR(s));
+
+    return true;
+    ERRHANDLER_CLOSE
+}
+
+bool pkpy_push_none(pkpy_vm vm_handle) {
+    VM* vm = (VM*) 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) {
     VM* vm = (VM*) vm_handle;
 
@@ -140,12 +262,18 @@ bool pkpy_set_global(pkpy_vm vm_handle, const char* name) {
     ERRHANDLER_CLOSE
 }
 
+//get global will also get bulitins
 bool pkpy_get_global(pkpy_vm vm_handle, const char* name) {
     VM* vm = (VM*) 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)
+            throw Exception("AttributeError", "could not find requested global");
+    }
 
     vm->c_data.push(o);
 
@@ -153,46 +281,58 @@ bool pkpy_get_global(pkpy_vm vm_handle, const char* name) {
     ERRHANDLER_CLOSE
 }
 
-static void call_wrapper(VM* vm, int argc, bool method_call) {
+
+bool pkpy_call(pkpy_vm vm_handle, int argc) {
+    VM* vm = (VM*) vm_handle;
+    ERRHANDLER_OPEN
+
     int callable_index = vm->c_data.size() - argc  - 1;
 
     PyObject* callable = vm->c_data.get(callable_index);
 
     vm->s_data.push(callable);
-    if (method_call) 
-        vm->s_data.push(vm->c_data.get(callable_index - 1));
-    else 
-        vm->s_data.push(PY_NULL);
+    vm->s_data.push(PY_NULL);
 
     for (int i = 0; i < argc; i++) 
         vm->s_data.push(vm->c_data.get(callable_index + i + 1));
 
     PyObject* o = vm->vectorcall(argc);
 
-    vm->c_data.shrink(argc + 1 + (int) method_call);
+    vm->c_data.shrink(argc + 1);
 
-    //TODO unpack tuple? 
-    vm->c_data.push(o);
-}
-
-bool pkpy_call(pkpy_vm vm_handle, int argc) {
-    VM* vm = (VM*) vm_handle;
-    ERRHANDLER_OPEN
-    call_wrapper(vm, argc, false);
+    unpack_return(vm, o);
 
     return true;
     ERRHANDLER_CLOSE
 }
 
-bool pkpy_call_method(pkpy_vm vm_handle, int argc) {
+bool pkpy_call_method(pkpy_vm vm_handle, const char* name, int argc) {
     VM* vm = (VM*) vm_handle;
     ERRHANDLER_OPEN
-    call_wrapper(vm, argc, true);
+
+    int self_index = vm->c_data.size() - argc  - 1;
+    PyObject* self = vm->c_data.get(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.get(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;
@@ -212,3 +352,99 @@ bool pkpy_to_int(pkpy_vm vm_handle, int index, int* ret) {
     return true;
     ERRHANDLER_CLOSE
 }
+
+bool pkpy_to_float(pkpy_vm vm_handle, int index, double* ret) {
+    VM* vm = (VM*) vm_handle;
+    ERRHANDLER_OPEN
+
+    index = lua_to_cstack_index(index, vm->c_data.size());
+
+    PyObject* o = vm->c_data.get(index);
+    if (ret != nullptr)
+        *ret = py_cast<double>(vm, o);
+
+    return true;
+    ERRHANDLER_CLOSE
+}
+
+bool pkpy_to_bool(pkpy_vm vm_handle, int index, bool* ret) {
+    VM* vm = (VM*) vm_handle;
+    ERRHANDLER_OPEN
+
+    index = lua_to_cstack_index(index, vm->c_data.size());
+
+    PyObject* o = vm->c_data.get(index);
+    if (ret != nullptr)
+        *ret = py_cast<bool>(vm, o);
+
+    return true;
+    ERRHANDLER_CLOSE
+}
+
+bool pkpy_to_string(pkpy_vm vm_handle, int index, char** ret) {
+    VM* vm = (VM*) vm_handle;
+    ERRHANDLER_OPEN
+
+    index = lua_to_cstack_index(index, vm->c_data.size());
+
+    PyObject* o = vm->c_data.get(index);
+    if (ret != nullptr) {
+        Str& s = CAST(Str&, o);
+        *ret = s.c_str_dup();
+    }
+
+    return true;
+    ERRHANDLER_CLOSE
+}
+
+bool pkpy_is_int(pkpy_vm vm_handle, int index) {
+    VM* vm = (VM*) vm_handle;
+
+    index = lua_to_cstack_index(index, vm->c_data.size());
+    PyObject* o = vm->c_data.get(index);
+
+    return is_type(o, vm->tp_int);
+}
+bool pkpy_is_float(pkpy_vm vm_handle, int index) {
+    VM* vm = (VM*) vm_handle;
+
+    index = lua_to_cstack_index(index, vm->c_data.size());
+    PyObject* o = vm->c_data.get(index);
+
+    return is_type(o, vm->tp_float);
+}
+bool pkpy_is_bool(pkpy_vm vm_handle, int index) {
+    VM* vm = (VM*) vm_handle;
+
+    index = lua_to_cstack_index(index, vm->c_data.size());
+    PyObject* o = vm->c_data.get(index);
+
+    return is_type(o, vm->tp_bool);
+}
+bool pkpy_is_string(pkpy_vm vm_handle, int index) {
+    VM* vm = (VM*) vm_handle;
+
+    index = lua_to_cstack_index(index, vm->c_data.size());
+    PyObject* o = vm->c_data.get(index);
+
+    return is_type(o, vm->tp_str);
+}
+bool pkpy_is_none(pkpy_vm vm_handle, int index) {
+    VM* vm = (VM*) vm_handle;
+
+    index = lua_to_cstack_index(index, vm->c_data.size());
+    PyObject* o = vm->c_data.get(index);
+
+    return o == vm->None;
+}
+
+bool pkpy_check_stack(pkpy_vm vm_handle, int free) {
+    VM* vm = (VM*) vm_handle;
+    return free <= vm->c_data.remaining();
+}
+
+int pkpy_stack_size(pkpy_vm vm_handle) {
+    VM* vm = (VM*) vm_handle;
+    return vm->c_data.size();
+}
+

+ 46 - 15
c_bindings/pocketpy_c.h

@@ -9,22 +9,30 @@ extern "C" {
 #include <stdint.h>
 
 typedef struct pkpy_vm_handle* pkpy_vm;
+typedef struct pkpy_repl_hande* pkpy_repl;
 
-//we mostly follow the lua api for these bindings
-//the key difference being most methods return a bool, true if it succeeded
-//false if it did not
+//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 this next method to check the error and clear it
-//if this method returns false it means that no error was set, and no action is taken
-//if it 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 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)
 //NOTE : you need to free the message that is passed back after you are done using it
 //or else pass in null as message, and it will just print the message to stderr
-bool pkpy_clear_error(pkpy_vm, const char** message);
+bool pkpy_clear_error(pkpy_vm, char** message);
 
 
-pkpy_vm pkpy_vm_create(bool use_stdio, bool enable_os);
-bool pkpy_vm_exec(pkpy_vm vm_handle, const char* source);
-void pkpy_vm_destroy(pkpy_vm vm);
+pkpy_vm pkpy_new_vm(bool use_stdio, bool enable_os);
+bool pkpy_vm_exec(pkpy_vm, const char* source);
+void pkpy_delete_vm(pkpy_vm);
+
+pkpy_repl pkpy_new_repl(pkpy_vm);
+//the repl does its own error management, so this method doesn't follow the ame
+//error semantics, it is just a thin wrapper around the REPL classes input method
+bool pkpy_repl_input(pkpy_repl, const char* line);
+void pkpy_delete_repl(pkpy_repl);
 
 
 typedef int (*pkpy_function)(pkpy_vm); 
@@ -32,28 +40,51 @@ typedef int (*pkpy_function)(pkpy_vm);
 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_none(pkpy_vm);
 
 bool pkpy_set_global(pkpy_vm, const char* name);
-bool pkpy_get_global(pkpy_vm vm_handle, 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 vm_handle, int argc);
+bool pkpy_call(pkpy_vm, int argc);
 
 //first push the object the method belongs to (self)
-//then push the callable you want to call
 //then push the the argments
 //argc is the number of arguments that was pushed (not counting the callable or self)
-bool pkpy_call_method(pkpy_vm vm_handle, int argc);
+//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 vm_handle, int index, int* ret);
+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);
+//you have to free ret after you are done using it
+bool pkpy_to_string(pkpy_vm, int index, char** ret);
+
+
+//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_none(pkpy_vm, int index);
+
+//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
 }

+ 262 - 0
c_bindings/test.c

@@ -0,0 +1,262 @@
+#include "pocketpy_c.h"
+#include <stdio.h>
+#include <stdlib.h>
+
+//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_delete_vm(vm);
+}
+
+int main(int argc, char** argv) {
+
+    vm = pkpy_new_vm(true, true);
+    atexit(cleanup);
+
+    //test exec
+    check(pkpy_vm_exec(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_exec(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));
+
+    printf("\ntesting float methods\n");
+    double r_float;
+    check(pkpy_push_float(vm, 11.11));
+    check(pkpy_set_global(vm, "elevenf"));
+    check(pkpy_vm_exec(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));
+
+    printf("\ntesting bool methods\n");
+    bool r_bool;
+    check(pkpy_push_bool(vm, false));
+    check(pkpy_set_global(vm, "false_test"));
+    check(pkpy_vm_exec(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));
+
+    printf("\ntesting string methods\n");
+    char* r_string;
+    check(pkpy_push_string(vm, "hello!"));
+    check(pkpy_set_global(vm, "hello1"));
+    check(pkpy_vm_exec(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);
+    fail(pkpy_is_int(vm, -1));
+    fail(pkpy_is_float(vm, -1));
+    fail(pkpy_is_bool(vm, -1));
+    fail(pkpy_is_none(vm, -1));
+    free(r_string);
+
+    printf("\ntesting None methods\n");
+    check(pkpy_push_none(vm));
+    check(pkpy_set_global(vm, "none"));
+    check(pkpy_vm_exec(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));
+
+    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, 251));
+    fail(pkpy_check_stack(vm, 252));
+    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_int(vm, -5));
+    check(pkpy_is_float(vm, -4));
+    check(pkpy_is_bool(vm, -3));
+    check(pkpy_is_string(vm, -2));
+    check(pkpy_is_none(vm, -1));
+    
+    printf("\ntesting error catching\n");
+    error(pkpy_vm_exec(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_exec(vm, "def x(x, y) : return x - y"));
+    check(pkpy_vm_exec(vm, "def vararg_x(*x) : return sum(x)"));
+    check(pkpy_vm_exec(vm, "def keyword_x(x=1, y=1) : return x+y"));
+    check(pkpy_vm_exec(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_exec(vm, "l = []"));
+
+    check(pkpy_get_global(vm, "l"));
+    check(pkpy_push_string(vm, "hello"));
+    check(pkpy_call_method(vm, "append", 1));
+    check(pkpy_vm_exec(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_exec(vm, "print(test_binding())"));
+
+    check(pkpy_push_function(vm, test_multiple_return));
+    check(pkpy_set_global(vm, "test_multiple_return"));
+    check(pkpy_vm_exec(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_exec(vm, "test_error_propagate()"));
+
+    check(pkpy_get_global(vm, "test_multiple_return"));
+    check(pkpy_call(vm, 0));
+    check(pkpy_stack_size(vm) == 2);
+
+    return 0;
+}

+ 49 - 0
c_bindings/test_answers.txt

@@ -0,0 +1,49 @@
+hello world!
+successfully errored with this message: 
+Traceback (most recent call last):
+AttributeError: could not find requested global
+
+testing int methods
+11
+11
+
+testing float methods
+11.11
+11.110000
+
+testing bool methods
+False
+0
+
+testing string methods
+hello!
+hello
+
+testing None methods
+None
+
+testing sizing and indexing
+stack size 5
+
+testing error catching
+successfully errored with this message: 
+  File "<c-bound>", 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
+ERROR: failed where it should have succeed at line 251
+clear error reported everything was fine

+ 24 - 0
run_c_binding_test.sh

@@ -0,0 +1,24 @@
+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, no weird output should show up"
+./c_binding_test > binding_test_scratch
+echo "checking results (they should be identical)"
+diff -s binding_test_scratch c_bindings/test_answers.txt
+
+echo "cleaning up"
+rm pocketpy_c.o
+rm test.o
+rm binding_test_scratch
+

+ 1 - 0
src/frame.h

@@ -117,6 +117,7 @@ struct CVirtualStack {
     PyObject** begin() { return _begin + offset; }
     PyObject** end() { return _sp; }
     void clear() { _sp = _begin + offset;}
+    int remaining() { return MAX_SIZE - (_sp - _begin); }
     
     size_t store() { size_t ret = offset; offset = _sp - _begin; return ret; }
     void restore(size_t stored) { offset = stored; }