diff --git a/docs/LuaC-API/introduction.md b/docs/LuaC-API/introduction.md index 38b9322d..5c2fede1 100644 --- a/docs/LuaC-API/introduction.md +++ b/docs/LuaC-API/introduction.md @@ -14,20 +14,26 @@ Special thanks for [@koltenpearson](https://github.com/koltenpearson) for bringi ## Basic Functions -#### `pkpy_vm* pkpy_vm_create(bool use_stdio, bool enable_os)` +#### `pkpy_vm* pkpy_new_vm(bool enable_os)` -Creates a new Lua Style VM. +Create a new VM. -+ `use_stdio`: if true, the VM will use stdout and stderr + `enable_os`: if true, the VM will have access to the os library -#### `bool pkpy_vm_run(pkpy_vm*, const char* source)` +#### `bool pkpy_vm_run(pkpy_vm* vm_handle, const char* source)` -Runs the given source code in the VM. +Run the given source code in the VM. + `source`: the source code to run -#### `void pkpy_vm_destroy(pkpy_vm*)` +#### `void pkpy_delete_vm(pkpy_vm* vm_handle)` -Destroys the VM. +Dispose the VM. +#### `bool pkpy_vm_exec(pkpy_vm* vm_handle, const char* source)` + +A wrapper of `vm->exec(...)`. + +#### `bool pkpy_vm_exec_2(pkpy_vm* vm_handle, const char* source, const char* filename, int mode, const char* module)` + +A wrapper of `vm->exec_2(...)`. \ No newline at end of file diff --git a/include/pocketpy/codeobject.h b/include/pocketpy/codeobject.h index 5d782c46..78230ee9 100644 --- a/include/pocketpy/codeobject.h +++ b/include/pocketpy/codeobject.h @@ -128,6 +128,29 @@ struct FuncDecl { void _gc_mark() const; }; +struct UserData{ + char data[16]; + bool empty; + + UserData(): empty(true) {} + template + UserData(T t): empty(false){ + static_assert(std::is_trivially_copyable_v); + static_assert(sizeof(T) <= sizeof(data)); + memcpy(data, &t, sizeof(T)); + } + + template + T get() const{ + static_assert(std::is_trivially_copyable_v); + static_assert(sizeof(T) <= sizeof(data)); +#if PK_DEBUG_EXTRA_CHECK + PK_ASSERT(!empty); +#endif + return reinterpret_cast(data); + } +}; + struct NativeFunc { NativeFuncC f; @@ -137,29 +160,16 @@ struct NativeFunc { // new style decl-based call FuncDecl_ decl; - using UserData = char[32]; UserData _userdata; - bool _has_userdata; - template - void set_userdata(T data) { - static_assert(std::is_trivially_copyable_v); - static_assert(sizeof(T) <= sizeof(UserData)); - if(_has_userdata) throw std::runtime_error("userdata already set"); - _has_userdata = true; - memcpy(_userdata, &data, sizeof(T)); + void set_userdata(UserData data) { + if(!_userdata.empty && !data.empty){ + // override is not supported + throw std::runtime_error("userdata already set"); + } + _userdata = data; } - template - T get_userdata() const { - static_assert(std::is_trivially_copyable_v); - static_assert(sizeof(T) <= sizeof(UserData)); -#if PK_DEBUG_EXTRA_CHECK - if(!_has_userdata) throw std::runtime_error("userdata not set"); -#endif - return reinterpret_cast(_userdata); - } - NativeFunc(NativeFuncC f, int argc, bool method); NativeFunc(NativeFuncC f, FuncDecl_ decl); @@ -201,8 +211,8 @@ struct Py_ final: PyObject { template T lambda_get_userdata(PyObject** p){ - if(p[-1] != PY_NULL) return PK_OBJ_GET(NativeFunc, p[-1]).get_userdata(); - else return PK_OBJ_GET(NativeFunc, p[-2]).get_userdata(); + if(p[-1] != PY_NULL) return PK_OBJ_GET(NativeFunc, p[-1])._userdata.get(); + else return PK_OBJ_GET(NativeFunc, p[-2])._userdata.get(); } } // namespace pkpy \ No newline at end of file diff --git a/include/pocketpy/frame.h b/include/pocketpy/frame.h index 95df9bcc..62b996f8 100644 --- a/include/pocketpy/frame.h +++ b/include/pocketpy/frame.h @@ -62,6 +62,9 @@ struct ValueStackImpl { } void clear() { _sp = _begin; } bool is_overflow() const { return _sp >= _max_end; } + + PyObject* operator[](int i) const { return _begin[i]; } + PyObject*& operator[](int i) { return _begin[i]; } ValueStackImpl(const ValueStackImpl&) = delete; ValueStackImpl(ValueStackImpl&&) = delete; diff --git a/include/pocketpy/pocketpy_c.h b/include/pocketpy/pocketpy_c.h index df7fc132..1917e817 100644 --- a/include/pocketpy/pocketpy_c.h +++ b/include/pocketpy/pocketpy_c.h @@ -14,37 +14,15 @@ typedef struct pkpy_vm_handle pkpy_vm; typedef int (*pkpy_function)(pkpy_vm*); /* Basic Functions */ -PK_EXPORT pkpy_vm* pkpy_vm_create(bool use_stdio, bool enable_os); -PK_EXPORT void pkpy_vm_destroy(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 -PK_EXPORT bool pkpy_clear_error(pkpy_vm*, char** message); -//NOTE you are responsible for freeing message - -//this will cause the vm to enter an error state and report the given message -//when queried -//note that at the moment this is more like a panic than throwing an error -//the user will not be able to catch it with python code -PK_EXPORT bool pkpy_error(pkpy_vm*, const char* name, const char* message); - - - - +PK_EXPORT pkpy_vm* pkpy_new_vm(bool enable_os); +PK_EXPORT void pkpy_delete_vm(pkpy_vm* vm); +PK_EXPORT bool pkpy_vm_exec(pkpy_vm* vm, const char* source); +PK_EXPORT bool pkpy_vm_exec_2(pkpy_vm* vm, const char* source, const char* filename, int mode, const char* module); +/* Stack Manipulation */ PK_EXPORT bool pkpy_pop(pkpy_vm*, int n); - -//push the item at index onto the top of the stack (as well as leaving it where -//it is on the stack) -PK_EXPORT bool pkpy_push(pkpy_vm*, int index); - +PK_EXPORT bool pkpy_dup_top(pkpy_vm*); +PK_EXPORT bool pkpy_rot_two(pkpy_vm*); PK_EXPORT bool pkpy_push_function(pkpy_vm*, pkpy_function, int); PK_EXPORT bool pkpy_push_int(pkpy_vm*, int); PK_EXPORT bool pkpy_push_float(pkpy_vm*, double); @@ -53,26 +31,32 @@ PK_EXPORT bool pkpy_push_string(pkpy_vm*, const char*); PK_EXPORT bool pkpy_push_stringn(pkpy_vm*, const char*, int length); PK_EXPORT bool pkpy_push_voidp(pkpy_vm*, void*); PK_EXPORT bool pkpy_push_none(pkpy_vm*); +PK_EXPORT bool pkpy_push_eval(pkpy_vm*, const char* source); +PK_EXPORT bool pkpy_push_module(pkpy_vm*, const char* name); + +/* Error Handling */ + +PK_EXPORT bool pkpy_clear_error(pkpy_vm*, char** message); +PK_EXPORT bool pkpy_error(pkpy_vm*, const char* name, const char* message); +//will return true if the vm is currently in an error state +PK_EXPORT bool pkpy_check_error(pkpy_vm*); + +/* Variables */ PK_EXPORT bool pkpy_set_global(pkpy_vm*, const char* name); PK_EXPORT bool pkpy_get_global(pkpy_vm*, const char* name); +//will return true if global exists +PK_EXPORT bool pkpy_check_global(pkpy_vm*, const char* name); +PK_EXPORT bool pkpy_getattr(pkpy_vm*, const char* name); +PK_EXPORT bool pkpy_setattr(pkpy_vm*, const char* name); + +/* Callables */ -//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) PK_EXPORT 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 PK_EXPORT bool pkpy_call_method(pkpy_vm*, const char* name, int argc); +/* Types */ -//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 PK_EXPORT bool pkpy_to_int(pkpy_vm*, int index, int* ret); PK_EXPORT bool pkpy_to_float(pkpy_vm*, int index, double* ret); PK_EXPORT bool pkpy_to_bool(pkpy_vm*, int index, bool* ret); @@ -97,34 +81,6 @@ PK_EXPORT bool pkpy_is_string(pkpy_vm*, int index); PK_EXPORT bool pkpy_is_voidp(pkpy_vm*, int index); PK_EXPORT bool pkpy_is_none(pkpy_vm*, int index); -//will return true if global exists -PK_EXPORT bool pkpy_check_global(pkpy_vm*, const char* name); - -//will return true if the vm is currently in an error state -PK_EXPORT bool pkpy_check_error(pkpy_vm*); - -//will return true if at least free empty slots remain on the stack -PK_EXPORT bool pkpy_check_stack(pkpy_vm*, int free); - -//returns the number of elements on the stack -PK_EXPORT int pkpy_stack_size(pkpy_vm*); - -PK_EXPORT bool pkpy_getattr(pkpy_vm*, const char* name); -PK_EXPORT bool pkpy_setattr(pkpy_vm*, const char* name); -PK_EXPORT bool pkpy_eval(pkpy_vm*, const char* source); - -// create a new native module with the given name and push it onto the stack -PK_EXPORT bool pkpy_new_module(void* vm, const char* name); - - -/* vm api */ - -// for backwards compatibility -#define pkpy_vm_run(vm, source) pkpy_vm_exec(vm, source) - -PK_EXPORT bool pkpy_vm_exec(pkpy_vm* vm, const char* source); -PK_EXPORT bool pkpy_vm_exec_2(pkpy_vm* vm, const char* source, const char* filename, int mode, const char* module); - /* special api */ // free a pointer allocated from pkpy's heap diff --git a/include/pocketpy/vm.h b/include/pocketpy/vm.h index 9c92c61e..a047a15f 100644 --- a/include/pocketpy/vm.h +++ b/include/pocketpy/vm.h @@ -117,9 +117,6 @@ public: NameDict _modules; // loaded modules std::map _lazy_modules; // lazy loaded modules - PyObject* _reg[32]; // registers for user purpose, also used by C-API - static constexpr int REG_COUNT = sizeof(_reg) / sizeof(void*); - PyObject* None; PyObject* True; PyObject* False; @@ -456,8 +453,8 @@ public: PyObject* _py_generator(Frame&& frame, ArgsView buffer); void _prepare_py_call(PyObject**, ArgsView, ArgsView, const FuncDecl_&); // new style binding api - PyObject* bind(PyObject*, const char*, const char*, NativeFuncC, void* userdata=nullptr); - PyObject* bind(PyObject*, const char*, NativeFuncC, void* userdata=nullptr); + PyObject* bind(PyObject*, const char*, const char*, NativeFuncC, UserData userdata={}); + PyObject* bind(PyObject*, const char*, NativeFuncC, UserData userdata={}); }; DEF_NATIVE_2(Str, tp_str) diff --git a/src/pocketpy_c.cpp b/src/pocketpy_c.cpp index fca6f1ce..7b637320 100644 --- a/src/pocketpy_c.cpp +++ b/src/pocketpy_c.cpp @@ -1,77 +1,60 @@ #include "pocketpy.h" +#include "pocketpy/tuplelist.h" #include "pocketpy_c.h" using namespace pkpy; typedef int (*LuaStyleFuncC)(VM*); -struct LuaStack: public ValueStackImpl<32>{ - PyObject*& at(int i) { - if(i < 0 || i >= size()){ - throw std::runtime_error("lua stack index out of range"); - } - return _begin[i]; - } - PyObject* const& at(int i) const { - if(i < 0 || i >= size()){ - throw std::runtime_error("lua stack index out of range"); - } - return _begin[i]; - } - - void safe_push(PyObject* obj){ - if(size() >= max_size()) throw std::runtime_error("lua stack overflow"); - push(obj); - } - - void safe_pop(){ - if(size() == 0) throw std::runtime_error("lua stack is empty"); - pop(); - } - - PyObject*& safe_top(){ - if(size() == 0) throw std::runtime_error("lua stack is empty"); - return top(); - } -}; - #define ERRHANDLER_OPEN \ - if (vm->error != nullptr) \ + if (vm->_c.error != nullptr) \ return false; \ try { #define ERRHANDLER_CLOSE \ } catch(Exception& e ) { \ - vm->error = py_var(vm, e); \ + vm->_c.error = py_var(vm, e); \ return false; \ } catch(const std::exception& re){ \ auto e = Exception("std::exception", re.what()); \ - vm->error = py_var(vm, e); \ + vm->_c.error = py_var(vm, e); \ return false; \ } +pkpy_vm* pkpy_new_vm(bool enable_os){ + return (pkpy_vm*)new VM(enable_os); +} -class CVM: public VM { -public: - LuaStack c_data; // operation stack - PyObject* error; +void pkpy_delete_vm(pkpy_vm* vm){ + return delete (VM*)vm; +} - CVM(bool use_stdio, bool enable_os) : VM(enable_os) { - error = nullptr; - heap._gc_marker_ex = [](VM* vm_) { - CVM* vm = (CVM*)vm_; - for(PyObject* obj: vm->c_data) if(obj!=nullptr) PK_OBJ_MARK(obj); - if(vm->error != nullptr) PK_OBJ_MARK(vm->error); - }; +bool pkpy_vm_exec(pkpy_vm* vm_handle, const char* source) { + VM* vm = (VM*) vm_handle; + PyObject* res; + ERRHANDLER_OPEN + CodeObject_ code = vm->compile(source, "main.py", EXEC_MODE); + res = vm->_exec(code, vm->_main); + ERRHANDLER_CLOSE + return res != nullptr; +} - if (!use_stdio) { - _stdout = _stderr = [](VM* vm, const Str& s){ - PK_UNUSED(vm); - PK_UNUSED(s); - }; - } +bool pkpy_vm_exec_2(pkpy_vm* vm_handle, const char* source, const char* filename, int mode, const char* module){ + VM* vm = (VM*) vm_handle; + PyObject* res; + PyObject* mod; + ERRHANDLER_OPEN + if(module == nullptr){ + mod = vm->_main; + }else{ + mod = vm->_modules[module]; // may raise } -}; + CodeObject_ code = vm->compile(source, filename, (CompileMode)mode); + res = vm->_exec(code, mod); + ERRHANDLER_CLOSE + return res != nullptr; +} + //for now I will unpack a tuple automatically, we may not want to handle @@ -114,89 +97,34 @@ bool pkpy_clear_error(pkpy_vm* vm_handle, char** message) { return true; } -pkpy_vm* pkpy_vm_create(bool use_stdio, bool enable_os) { - CVM* vm = new CVM(use_stdio, enable_os); - return (pkpy_vm*) vm; -} - -bool pkpy_vm_exec(pkpy_vm* vm_handle, const char* source) { - CVM* vm = (CVM*) vm_handle; - PyObject* res; - ERRHANDLER_OPEN - CodeObject_ code = vm->compile(source, "main.py", EXEC_MODE); - res = vm->_exec(code, vm->_main); - ERRHANDLER_CLOSE - return res != nullptr; -} - -bool pkpy_vm_exec_2(pkpy_vm* vm_handle, const char* source, const char* filename, int mode, const char* module){ - CVM* vm = (CVM*) vm_handle; - PyObject* res; - PyObject* mod; - ERRHANDLER_OPEN - if(module == nullptr){ - mod = vm->_main; - }else{ - mod = vm->_modules[module]; // may raise - } - CodeObject_ code = vm->compile(source, filename, (CompileMode)mode); - res = vm->_exec(code, mod); - ERRHANDLER_CLOSE - return res != nullptr; -} - -void pkpy_vm_destroy(pkpy_vm* vm_handle) { - CVM* vm = (CVM*) vm_handle; - delete vm; -} - PyObject* c_function_wrapper(VM* vm, ArgsView args) { LuaStyleFuncC f = lambda_get_userdata(args.begin()); - CVM* cvm = (CVM*) vm; - - //setup c stack - LuaStack local_stack; - - for (int i = 0; i < args.size(); i++) - local_stack.safe_push(args[i]); - - // tmp is controlled by RAII - auto tmp = CVM::TempStack(cvm, &local_stack); - int retc = f(cvm); - + PyObject** curr_sp = &vm->s_data.top(); + int retc = f(vm); // propagate_if_errored - if (cvm->error != nullptr){ - Exception e = _py_cast(vm, cvm->error); - cvm->error = nullptr; - tmp.restore(); + if (vm->_c.error != nullptr){ + Exception e = _py_cast(vm, vm->_c.error); + vm->_c.error = nullptr; vm->_error(e); } - tmp.restore(); - - PyObject* ret = cvm->None; - - if (retc == 1) - ret = local_stack.safe_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.at(stack_index); - } - - ret = py_var(cvm, t); - } - - return ret; + PK_ASSERT(retc == vm->s_data._sp-curr_sp); + if(retc == 0) return vm->None; + if (retc == 1) return vm->s_data.popx(); + ArgsView ret_view(curr_sp, vm->s_data._sp); + return py_var(vm, ret_view.to_tuple()); } -bool pkpy_push_function(pkpy_vm* vm_handle, pkpy_function f, int argc) { - CVM* vm = (CVM*) vm_handle; - NativeFunc nf = NativeFunc(c_function_wrapper, argc, false); - nf.set_userdata(f); +bool pkpy_push_function(pkpy_vm* vm_handle, const char* sig, pkpy_function f) { + VM* vm = (VM*) vm_handle; ERRHANDLER_OPEN - vm->c_data->safe_push(py_var(vm, nf)); + PyObject* f_obj = vm->bind( + nullptr, + sig, + nullptr, + c_function_wrapper, + f + ); + vm->s_data.push(f_obj); ERRHANDLER_CLOSE return true; } diff --git a/src/vm.cpp b/src/vm.cpp index 47284c01..d2c3fea3 100644 --- a/src/vm.cpp +++ b/src/vm.cpp @@ -4,7 +4,6 @@ namespace pkpy{ VM::VM(bool enable_os) : heap(this), enable_os(enable_os) { this->vm = this; - for(int i=0; iattr().set(name, value); } -PyObject* VM::bind(PyObject* obj, const char* sig, NativeFuncC fn, void* userdata){ +PyObject* VM::bind(PyObject* obj, const char* sig, NativeFuncC fn, UserData userdata){ return bind(obj, sig, nullptr, fn, userdata); } -PyObject* VM::bind(PyObject* obj, const char* sig, const char* docstring, NativeFuncC fn, void* userdata){ +PyObject* VM::bind(PyObject* obj, const char* sig, const char* docstring, NativeFuncC fn, UserData userdata){ CodeObject_ co; try{ // fn(a, b, *c, d=1) -> None @@ -978,10 +977,8 @@ PyObject* VM::bind(PyObject* obj, const char* sig, const char* docstring, Native decl->docstring = Str(docstring).strip(); } PyObject* f_obj = VAR(NativeFunc(fn, decl)); - if(userdata != nullptr){ - PK_OBJ_GET(NativeFunc, f_obj).set_userdata(userdata); - } - obj->attr().set(decl->code->name, f_obj); + PK_OBJ_GET(NativeFunc, f_obj).set_userdata(userdata); + if(obj != nullptr) obj->attr().set(decl->code->name, f_obj); return f_obj; } @@ -1000,9 +997,6 @@ void ManagedHeap::mark() { for(PyObject* obj: vm->s_data) PK_OBJ_MARK(obj); if(_gc_marker_ex) _gc_marker_ex(vm); if(vm->_last_exception) PK_OBJ_MARK(vm->_last_exception); - for(int i=0; iREG_COUNT; i++){ - if(vm->_reg[i] != nullptr) PK_OBJ_MARK(vm->_reg[i]); - } } Str obj_type_name(VM *vm, Type type){