diff --git a/build_cpp.sh b/build_cpp.sh index a4d1a5ff..7886562e 100644 --- a/build_cpp.sh +++ b/build_cpp.sh @@ -1 +1 @@ -g++ -o pocketpy src/main.cpp --std=c++17 -O1 -pthread -Wall -Wno-sign-compare -Wno-unused-variable -fno-rtti \ No newline at end of file +g++ -o pocketpy src/main.cpp --std=c++17 -O1 -Wall -Wno-sign-compare -Wno-unused-variable -fno-rtti \ No newline at end of file diff --git a/src/__stl__.h b/src/__stl__.h index c1753312..94489aec 100644 --- a/src/__stl__.h +++ b/src/__stl__.h @@ -34,8 +34,6 @@ #ifdef __EMSCRIPTEN__ #include -#else -#include #endif #define PK_VERSION "0.6.2" diff --git a/src/main.cpp b/src/main.cpp index 58b3c02e..286aab28 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -3,8 +3,7 @@ #include "pocketpy.h" -#define PK_DEBUG_TIME -//#define PK_DEBUG_THREADED +//#define PK_DEBUG_TIME struct Timer{ const char* title; @@ -22,53 +21,20 @@ struct Timer{ } }; -void _tvm_dispatch(ThreadedVM* vm){ - while(pkpy_tvm_get_state(vm) != THREAD_FINISHED){ - if(pkpy_tvm_get_state(vm) == THREAD_SUSPENDED){ - char* obj = pkpy_tvm_read_jsonrpc_request(vm); - // this is not safe, but it's ok for demo - bool is_input_call = std::string_view(obj).find("\"input\"") != std::string::npos; - if(is_input_call){ - std::string line; - std::getline(std::cin, line); - _StrStream ss; - ss << '{'; - ss << "\"result\": " << _Str(line).__escape(false); - ss << '}'; - pkpy_tvm_write_jsonrpc_response(vm, ss.str().c_str()); - }else{ - std::cout << "unknown jsonrpc call" << std::endl; - std::cout << obj << std::endl; - exit(3); - } - pkpy_delete(obj); - } - } -} + #ifndef __NO_MAIN int main(int argc, char** argv){ if(argc == 1){ -#ifndef PK_DEBUG_THREADED VM* vm = pkpy_new_vm(true); -#else - ThreadedVM* vm = pkpy_new_tvm(true); -#endif REPL repl(vm); int result = -1; while(true){ (*vm->_stdout) << (result==0 ? "... " : ">>> "); std::string line; std::getline(std::cin, line); - pkpy_repl_input(&repl, line.c_str()); - result = pkpy_repl_last_input_result(&repl); -#ifdef PK_DEBUG_THREADED - if(result == (int)EXEC_STARTED){ - _tvm_dispatch(vm); - pkpy_tvm_reset_state(vm); - } -#endif + result = pkpy_repl_input(&repl, line.c_str()); } return 0; } @@ -84,18 +50,10 @@ int main(int argc, char** argv){ } std::string src((std::istreambuf_iterator(file)), std::istreambuf_iterator()); - ThreadedVM* vm = pkpy_new_tvm(true); -#ifdef PK_DEBUG_THREADED - Timer("Running time").run([=]{ - vm->execAsync(src.c_str(), filename, EXEC_MODE); - _tvm_dispatch(vm); - }); -#else + VM* vm = pkpy_new_vm(true); Timer("Running time").run([=]{ vm->exec(src.c_str(), filename, EXEC_MODE); }); -#endif - pkpy_delete(vm); return 0; } diff --git a/src/parser.h b/src/parser.h index 5168ffb8..223d0859 100644 --- a/src/parser.h +++ b/src/parser.h @@ -28,9 +28,7 @@ constexpr _TokenType TK(const char* const token) { for(int k=0; k<__TOKENS_LEN; k++){ const char* i = __TOKENS[k]; const char* j = token; - while(*i && *j && *i == *j){ - i++; j++; - } + while(*i && *j && *i == *j) { i++; j++;} if(*i == *j) return k; } return 0; @@ -101,9 +99,7 @@ struct Parser { std::queue nexts; std::stack indents; - int brackets_level_0 = 0; - int brackets_level_1 = 0; - int brackets_level_2 = 0; + int brackets_level = 0; Token next_token(){ if(nexts.empty()){ @@ -143,7 +139,7 @@ struct Parser { } bool eat_indentation(){ - if(brackets_level_0 > 0 || brackets_level_1 > 0 || brackets_level_2 > 0) return true; + if(brackets_level > 0) return true; int spaces = eat_spaces(); if(peekchar() == '#') skip_line_comment(); if(peekchar() == '\0' || peekchar() == '\n') return true; @@ -272,16 +268,10 @@ struct Parser { // Initialize the next token as the type. void set_next_token(_TokenType type, PyVar value=nullptr) { - switch(type){ - case TK("("): brackets_level_0++; break; - case TK(")"): brackets_level_0--; break; - case TK("["): brackets_level_1++; break; - case TK("]"): brackets_level_1--; break; - case TK("{"): brackets_level_2++; break; - case TK("}"): brackets_level_2--; break; + case TK("{"): case TK("["): case TK("("): brackets_level++; break; + case TK(")"): case TK("]"): case TK("}"): brackets_level--; break; } - nexts.push( Token{ type, token_start, diff --git a/src/pocketpy.h b/src/pocketpy.h index e66e0dc4..e79b27fa 100644 --- a/src/pocketpy.h +++ b/src/pocketpy.h @@ -610,16 +610,6 @@ void __addModuleTime(VM* vm){ auto now = std::chrono::high_resolution_clock::now(); return vm->PyFloat(std::chrono::duration_cast(now.time_since_epoch()).count() / 1000000.0); }); - - vm->bindFunc(mod, "sleep", [](VM* vm, const pkpy::ArgList& args) { - vm->check_args_size(args, 1); - if(!vm->is_int_or_float(args[0])){ - vm->typeError("time.sleep() argument must be int or float"); - } - double sec = vm->num_to_float(args[0]); - vm->sleepForSecs(sec); - return vm->None; - }); } void __addModuleSys(VM* vm){ @@ -833,7 +823,7 @@ public: extern "C" { __EXPORT /// Delete a pointer allocated by `pkpy_xxx_xxx`. - /// It can be `VM*`, `REPL*`, `ThreadedVM*`, `char*`, etc. + /// It can be `VM*`, `REPL*`, `char*`, etc. /// /// !!! /// If the pointer is not allocated by `pkpy_xxx_xxx`, the behavior is undefined. @@ -896,14 +886,8 @@ extern "C" { __EXPORT /// Input a source line to an interactive console. - void pkpy_repl_input(REPL* r, const char* line){ - r->input(line); - } - - __EXPORT - /// Check if the REPL needs more lines. - int pkpy_repl_last_input_result(REPL* r){ - return (int)(r->last_input_result()); + int pkpy_repl_input(REPL* r, const char* line){ + return r->input(line); } __EXPORT @@ -936,14 +920,6 @@ extern "C" { return vm; } - __EXPORT - /// Create a virtual machine that supports asynchronous execution. - ThreadedVM* pkpy_new_tvm(bool use_stdio){ - ThreadedVM* vm = pkpy_allocate(ThreadedVM, use_stdio); - __vm_init(vm); - return vm; - } - __EXPORT /// Read the standard output and standard error as string of a virtual machine. /// The `vm->use_stdio` should be `false`. @@ -964,48 +940,4 @@ extern "C" { s_err->str(""); return strdup(ss.str().c_str()); } - - __EXPORT - /// Get the current state of a threaded virtual machine. - /// - /// Return `0` for `THREAD_READY`, - /// `1` for `THREAD_RUNNING`, - /// `2` for `THREAD_SUSPENDED`, - /// `3` for `THREAD_FINISHED`. - int pkpy_tvm_get_state(ThreadedVM* vm){ - return vm->getState(); - } - - __EXPORT - /// Set the state of a threaded virtual machine to `THREAD_READY`. - /// The current state should be `THREAD_FINISHED`. - void pkpy_tvm_reset_state(ThreadedVM* vm){ - vm->resetState(); - } - - __EXPORT - /// Read the current JSONRPC request from shared string buffer. - char* pkpy_tvm_read_jsonrpc_request(ThreadedVM* vm){ - _Str s = vm->readJsonRpcRequest(); - return strdup(s.c_str()); - } - - __EXPORT - /// Write a JSONRPC response to shared string buffer. - void pkpy_tvm_write_jsonrpc_response(ThreadedVM* vm, const char* value){ - vm->writeJsonrpcResponse(value); - } - - __EXPORT - /// Emit a KeyboardInterrupt signal to stop a running threaded virtual machine. - void pkpy_tvm_terminate(ThreadedVM* vm){ - vm->terminate(); - } - - __EXPORT - /// Run a given source on a threaded virtual machine. - /// The excution will be started in a new thread. - void pkpy_tvm_exec_async(VM* vm, const char* source){ - vm->execAsync(source, "main.py", EXEC_MODE); - } } \ No newline at end of file diff --git a/src/repl.h b/src/repl.h index 84cb7fd9..692b56bb 100644 --- a/src/repl.h +++ b/src/repl.h @@ -14,7 +14,6 @@ protected: int need_more_lines = 0; std::string buffer; VM* vm; - InputResult lastResult = EXEC_SKIPPED; public: REPL(VM* vm) : vm(vm){ (*vm->_stdout) << ("pocketpy " PK_VERSION " (" __DATE__ ", " __TIME__ ")\n"); @@ -22,11 +21,7 @@ public: (*vm->_stdout) << ("Type \"exit()\" to exit." "\n"); } - InputResult last_input_result() const { - return lastResult; - } - - void input(std::string line){ + InputResult input(std::string line){ if(need_more_lines){ buffer += line; buffer += '\n'; @@ -40,15 +35,10 @@ public: buffer.clear(); }else{ __NOT_ENOUGH_LINES: - lastResult = NEED_MORE_LINES; - return; + return NEED_MORE_LINES; } }else{ - if(line == "exit()") exit(0); - if(line.empty()) { - lastResult = EXEC_SKIPPED; - return; - } + if(line.empty()) return EXEC_SKIPPED; } try{ @@ -58,15 +48,11 @@ __NOT_ENOUGH_LINES: buffer += line; buffer += '\n'; need_more_lines = ne.isClassDef ? 3 : 2; - if (need_more_lines) { - lastResult = NEED_MORE_LINES; - } - return; + if (need_more_lines) return NEED_MORE_LINES; }catch(...){ // do nothing } - - lastResult = EXEC_STARTED; - vm->execAsync(line, "", SINGLE_MODE); + vm->exec(line, "", SINGLE_MODE); + return EXEC_STARTED; } }; \ No newline at end of file diff --git a/src/vm.h b/src/vm.h index c6bbd52d..5314cc30 100644 --- a/src/vm.h +++ b/src/vm.h @@ -21,7 +21,6 @@ class VM { - std::atomic _stop_flag = false; std::vector _small_integers; // [-5, 256] PyVarDict _modules; // loaded modules emhash8::HashMap<_Str, _Str> _lazy_modules; // lazy loaded modules @@ -29,21 +28,11 @@ protected: std::deque< std::unique_ptr > callstack; PyVar __py2py_call_signal; - inline void test_stop_flag(){ - if(_stop_flag){ - _stop_flag = false; - _error("KeyboardInterrupt", ""); - } - } - PyVar run_frame(Frame* frame){ while(frame->has_next_bytecode()){ const Bytecode& byte = frame->next_bytecode(); //printf("[%d] %s (%d)\n", frame->stack_size(), OP_NAMES[byte.op], byte.arg); //printf("%s\n", frame->code->src->getLine(byte.line).c_str()); - - test_stop_flag(); - switch (byte.op) { case OP_NO_OP: break; // do nothing @@ -380,22 +369,6 @@ public: for(i64 i=-5; i<=256; i++) _small_integers.push_back(new_object(_tp_int, i)); } - void keyboardInterrupt(){ - _stop_flag = true; - } - - void sleepForSecs(f64 sec){ - i64 ms = (i64)(sec * 1000); - for(i64 i=0; i maxRecursionDepth){ @@ -737,7 +706,8 @@ public: }else if(obj->is_type(_tp_float)){ return PyFloat_AS_C(obj); } - UNREACHABLE(); + typeError("expected int or float"); + return 0; } PyVar num_negated(const PyVar& obj){ @@ -925,9 +895,7 @@ public: i64 x = 1000003; for (const auto& item : PyTuple_AS_C(obj)) { i64 y = hash(item); - // this is recommended by Github Copilot - // i am not sure whether it is a good idea - x = x ^ (y + 0x9e3779b9 + (x << 6) + (x >> 2)); + x = x ^ (y + 0x9e3779b9 + (x << 6) + (x >> 2)); // recommended by Github Copilot } return x; } @@ -1023,10 +991,10 @@ void NameRef::del(VM* vm, Frame* frame) const{ } break; case NAME_GLOBAL: { - if(frame->f_locals.count(pair->first) > 0){ + if(frame->f_locals.contains(pair->first)){ frame->f_locals.erase(pair->first); }else{ - if(frame->f_globals().count(pair->first) > 0){ + if(frame->f_globals().contains(pair->first)){ frame->f_globals().erase(pair->first); }else{ vm->nameError(pair->first); @@ -1099,122 +1067,4 @@ PyVar RangeIterator::next(){ PyVar StringIterator::next(){ return vm->PyStr(str.u8_getitem(index++)); -} - -enum ThreadState { - THREAD_READY, - THREAD_RUNNING, - THREAD_SUSPENDED, - THREAD_FINISHED -}; - -class ThreadedVM : public VM { - std::atomic _state = THREAD_READY; - _Str _sharedStr = ""; - -#ifndef __EMSCRIPTEN__ - std::thread* _thread = nullptr; - void __deleteThread(){ - if(_thread != nullptr){ - terminate(); - _thread->join(); - delete _thread; - _thread = nullptr; - } - } -#else - void __deleteThread(){ - terminate(); - } -#endif - -public: - ThreadedVM(bool use_stdio) : VM(use_stdio) { - bindBuiltinFunc("__string_channel_call", [](VM* vm, const pkpy::ArgList& args){ - vm->check_args_size(args, 1); - _Str data = vm->PyStr_AS_C(args[0]); - - ThreadedVM* tvm = (ThreadedVM*)vm; - tvm->_sharedStr = data; - tvm->suspend(); - return tvm->PyStr(tvm->readJsonRpcRequest()); - }); - } - - void terminate(){ - if(_state == THREAD_RUNNING || _state == THREAD_SUSPENDED){ - keyboardInterrupt(); -#ifdef __EMSCRIPTEN__ - // no way to terminate safely -#else - while(_state != THREAD_FINISHED); -#endif - } - } - - void suspend(){ - if(_state != THREAD_RUNNING) UNREACHABLE(); - _state = THREAD_SUSPENDED; - while(_state == THREAD_SUSPENDED){ - test_stop_flag(); -#ifdef __EMSCRIPTEN__ - emscripten_sleep(20); -#else - std::this_thread::sleep_for(std::chrono::milliseconds(20)); -#endif - } - } - - _Str readJsonRpcRequest(){ - _Str copy = _sharedStr; - _sharedStr = ""; - return copy; - } - - /***** For outer use *****/ - - ThreadState getState(){ - return _state; - } - - void writeJsonrpcResponse(const char* value){ - if(_state != THREAD_SUSPENDED) UNREACHABLE(); - _sharedStr = _Str(value); - _state = THREAD_RUNNING; - } - - void execAsync(_Str source, _Str filename, CompileMode mode) override { - if(_state != THREAD_READY) UNREACHABLE(); - -#ifdef __EMSCRIPTEN__ - this->_state = THREAD_RUNNING; - VM::exec(source, filename, mode); - this->_state = THREAD_FINISHED; -#else - __deleteThread(); - _thread = new std::thread([=](){ - this->_state = THREAD_RUNNING; - VM::exec(source, filename, mode); - this->_state = THREAD_FINISHED; - }); -#endif - } - - PyVarOrNull exec(_Str source, _Str filename, CompileMode mode, PyVar _module=nullptr) override { - if(_state == THREAD_READY) return VM::exec(source, filename, mode, _module); - auto callstackBackup = std::move(callstack); - callstack.clear(); - PyVarOrNull ret = VM::exec(source, filename, mode, _module); - callstack = std::move(callstackBackup); - return ret; - } - - void resetState(){ - if(this->_state != THREAD_FINISHED) return; - this->_state = THREAD_READY; - } - - ~ThreadedVM(){ - __deleteThread(); - } -}; \ No newline at end of file +} \ No newline at end of file diff --git a/web/index.js b/web/index.js index d133aa39..90512986 100644 --- a/web/index.js +++ b/web/index.js @@ -34,8 +34,7 @@ function term_init() { term.write("Bye!\r\n"); break; } - Module.ccall('pkpy_repl_input', 'number', ['number', 'string'], [repl, command]); - need_more_lines = Module.ccall('pkpy_repl_last_input_result', 'number', ['number'], [repl]) == 0; + need_more_lines = Module.ccall('pkpy_repl_input', 'number', ['number', 'string'], [repl, command]) == 0; command = ''; term.write(need_more_lines ? "... " : ">>> "); break;