From 16f46dec82ce6c641aff120c9be3924c5edf61b4 Mon Sep 17 00:00:00 2001 From: blueloveTH Date: Thu, 8 Dec 2022 19:44:14 +0800 Subject: [PATCH] impl kwargs --- src/codeobject.h | 6 +- src/compiler.h | 25 +++++-- src/error.h | 4 +- src/main.cpp | 5 +- src/pocketpy.h | 12 ++-- src/safestl.h | 42 +++++++++--- src/vm.h | 158 +++++++++++++++++++++++++-------------------- tests/functions.py | 21 ++++++ 8 files changed, 181 insertions(+), 92 deletions(-) diff --git a/src/codeobject.h b/src/codeobject.h index eedce55e..2af2ab80 100644 --- a/src/codeobject.h +++ b/src/codeobject.h @@ -133,11 +133,15 @@ public: uint64_t id; + inline PyVarDict copy_f_locals(){ + return f_locals; + } + inline PyVarDict& f_globals(){ return _module->attribs; } - Frame(const CodeObject* code, PyVar _module, const PyVarDict& locals) + Frame(const CodeObject* code, PyVar _module, PyVarDict&& locals) : code(code), _module(_module), f_locals(locals) { static uint64_t frame_id = 1; diff --git a/src/compiler.h b/src/compiler.h index aaddc23e..84feae20 100644 --- a/src/compiler.h +++ b/src/compiler.h @@ -276,10 +276,16 @@ public: parser->setNextToken(TK("@eof")); } - _TokenType peek() { + inline _TokenType peek() { return parser->current.type; } + // not sure this will work + _TokenType peekNext() { + if(parser->nexts.empty()) return TK("@eof"); + return parser->nexts.front().type; + } + bool match(_TokenType expected) { if (peek() != expected) return false; lexToken(); @@ -545,15 +551,26 @@ __LISTCOMP: void exprCall() { int ARGC = 0; + int KWARGC = 0; do { matchNewLines(mode()==SINGLE_MODE); if (peek() == TK(")")) break; - EXPR(); - ARGC++; + if(peek() == TK("@id") && peekNext() == TK("=")) { + consume(TK("@id")); + const _Str& key = parser->previous.str(); + emitCode(OP_LOAD_CONST, getCode()->addConst(vm->PyStr(key))); + consume(TK("=")); + EXPR(); + KWARGC++; + } else{ + if(KWARGC > 0) syntaxError("positional argument follows keyword argument"); + EXPR(); + ARGC++; + } matchNewLines(mode()==SINGLE_MODE); } while (match(TK(","))); consume(TK(")")); - emitCode(OP_CALL, ARGC); + emitCode(OP_CALL, (KWARGC << 16) | ARGC); } void exprName() { diff --git a/src/error.h b/src/error.h index a66f8ecf..c28ceb04 100644 --- a/src/error.h +++ b/src/error.h @@ -23,7 +23,9 @@ struct SourceMetadata { _Str getLine(int lineno) const { if(lineno == -1) return ""; - const char* _start = lineStarts.at(lineno-1); + lineno -= 1; + if(lineno < 0) lineno = 0; + const char* _start = lineStarts.at(lineno); const char* i = _start; while(*i != '\n' && *i != '\0') i++; return _Str(_start, i-_start); diff --git a/src/main.cpp b/src/main.cpp index 347b1562..e8d29d87 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -3,8 +3,8 @@ #include "pocketpy.h" -#define PK_DEBUG_TIME -//#define PK_DEBUG_THREADED +//#define PK_DEBUG_TIME +#define PK_DEBUG_THREADED struct Timer{ const char* title; @@ -46,6 +46,7 @@ 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; diff --git a/src/pocketpy.h b/src/pocketpy.h index 0f2127ce..0ad26c5f 100644 --- a/src/pocketpy.h +++ b/src/pocketpy.h @@ -56,7 +56,7 @@ void __initializeBuiltinFunctions(VM* _vm) { vm->__checkArgSize(args, 1); const _Str& expr = vm->PyStr_AS_C(args[0]); _Code code = compile(vm, expr.c_str(), "", EVAL_MODE, false); - return vm->_exec(code, vm->topFrame()->_module, vm->topFrame()->f_locals); + return vm->_exec(code, vm->topFrame()->_module, vm->topFrame()->copy_f_locals()); }); _vm->bindBuiltinFunc("isinstance", [](VM* vm, const pkpy::ArgList& args) { @@ -91,9 +91,9 @@ void __initializeBuiltinFunctions(VM* _vm) { _vm->bindBuiltinFunc("globals", [](VM* vm, const pkpy::ArgList& args) { vm->__checkArgSize(args, 0); const auto& d = vm->topFrame()->f_globals(); - PyVar obj = vm->call(vm->builtins->attribs["dict"], {}); + PyVar obj = vm->call(vm->builtins->attribs["dict"]); for (const auto& [k, v] : d) { - vm->call(obj, __setitem__, {vm->PyStr(k), v}); + vm->call(obj, __setitem__, pkpy::twoArgs(vm->PyStr(k), v)); } return obj; }); @@ -101,9 +101,9 @@ void __initializeBuiltinFunctions(VM* _vm) { _vm->bindBuiltinFunc("locals", [](VM* vm, const pkpy::ArgList& args) { vm->__checkArgSize(args, 0); const auto& d = vm->topFrame()->f_locals; - PyVar obj = vm->call(vm->builtins->attribs["dict"], {}); + PyVar obj = vm->call(vm->builtins->attribs["dict"]); for (const auto& [k, v] : d) { - vm->call(obj, __setitem__, {vm->PyStr(k), v}); + vm->call(obj, __setitem__, pkpy::twoArgs(vm->PyStr(k), v)); } return obj; }); @@ -642,7 +642,7 @@ void __addModuleJson(VM* vm){ vm->__checkArgSize(args, 1); const _Str& expr = vm->PyStr_AS_C(args[0]); _Code code = compile(vm, expr.c_str(), "", JSON_MODE, false); - return vm->_exec(code, vm->topFrame()->_module, vm->topFrame()->f_locals); + return vm->_exec(code, vm->topFrame()->_module, vm->topFrame()->copy_f_locals()); }); vm->bindFunc(mod, "dumps", [](VM* vm, const pkpy::ArgList& args) { diff --git a/src/safestl.h b/src/safestl.h index 1701511c..fafde218 100644 --- a/src/safestl.h +++ b/src/safestl.h @@ -58,7 +58,7 @@ public: } #endif - PyVarDict() : emhash8::HashMap<_Str, PyVar>() {} + using emhash8::HashMap<_Str, PyVar>::HashMap; }; @@ -128,13 +128,6 @@ namespace pkpy { other.clear(); } - // deprecated, this is very slow, do not use it!!! - ArgList(std::initializer_list args){ - __tryAlloc(args.size()); - uint8_t i = 0; - for(auto& arg: args) this->_args[i++] = arg; - } - PyVar& operator[](uint8_t i){ __checkIndex(i); return _args[i]; @@ -161,7 +154,7 @@ namespace pkpy { return *this; } - uint8_t size() const { + inline uint8_t size() const { return _size; } @@ -186,4 +179,35 @@ namespace pkpy { __tryRelease(); } }; + + const ArgList& noArg(){ + static ArgList ret(0); + return ret; + } + + ArgList oneArg(PyVar&& a) { + ArgList ret(1); + ret[0] = std::move(a); + return ret; + } + + ArgList oneArg(const PyVar& a) { + ArgList ret(1); + ret[0] = a; + return ret; + } + + ArgList twoArgs(PyVar&& a, PyVar&& b) { + ArgList ret(2); + ret[0] = std::move(a); + ret[1] = std::move(b); + return ret; + } + + ArgList twoArgs(const PyVar& a, const PyVar& b) { + ArgList ret(2); + ret[0] = a; + ret[1] = b; + return ret; + } } \ No newline at end of file diff --git a/src/vm.h b/src/vm.h index c42ba473..ee019a72 100644 --- a/src/vm.h +++ b/src/vm.h @@ -77,7 +77,7 @@ protected: } break; case OP_BUILD_INDEX_PTR: { PyVar index = frame->popValue(this); - PyVar obj = frame->popValue(this); + VarRef obj = frame->popValue(this); frame->push(PyPointer(IndexPointer(obj, index))); } break; case OP_STORE_PTR: { @@ -182,8 +182,7 @@ protected: case OP_CONTAINS_OP: { PyVar rhs = frame->popValue(this); - PyVar lhs = frame->popValue(this); - bool ret_c = PyBool_AS_C(call(rhs, __contains__, {std::move(lhs)})); + bool ret_c = PyBool_AS_C(call(rhs, __contains__, pkpy::oneArg(frame->popValue(this)))); if(byte.arg == 1) ret_c = !ret_c; frame->push(PyBool(ret_c)); } break; @@ -241,18 +240,22 @@ protected: case OP_BUILD_MAP: { pkpy::ArgList items = frame->popNValuesReversed(this, byte.arg*2); - PyVar obj = call(builtins->attribs["dict"], {}); + PyVar obj = call(builtins->attribs["dict"]); for(int i=0; ipush(obj); } break; case OP_DUP_TOP: frame->push(frame->topValue(this)); break; case OP_CALL: { - pkpy::ArgList args = frame->popNValuesReversed(this, byte.arg); + int ARGC = byte.arg & 0xFFFF; + int KWARGC = (byte.arg >> 16) & 0xFFFF; + pkpy::ArgList kwargs(0); + if(KWARGC > 0) kwargs = frame->popNValuesReversed(this, KWARGC*2); + pkpy::ArgList args = frame->popNValuesReversed(this, ARGC); PyVar callable = frame->popValue(this); - PyVar ret = call(std::move(callable), std::move(args), true); + PyVar ret = call(callable, std::move(args), kwargs, true); if(ret == __py2py_call_signal) return ret; frame->push(std::move(ret)); } break; @@ -272,7 +275,7 @@ protected: PyVar obj = frame->popValue(this); PyVarOrNull iter_fn = getAttr(obj, __iter__, false); if(iter_fn != nullptr){ - PyVar tmp = call(iter_fn, {obj}); + PyVar tmp = call(iter_fn, pkpy::oneArg(obj)); VarRef var = frame->__pop(); __checkType(var, _tp_pointer); PyIter_AS_C(tmp)->var = var; @@ -333,18 +336,8 @@ protected: frame->push(it->second); } } break; - case OP_WITH_ENTER: - { - PyVar obj = frame->popValue(this); - PyVar enter_fn = getAttr(obj, "__enter__"_c); - call(enter_fn, {}); - } break; - case OP_WITH_EXIT: - { - PyVar obj = frame->popValue(this); - PyVar exit_fn = getAttr(obj, "__exit__"_c); - call(exit_fn, {}); - } break; + case OP_WITH_ENTER: call(frame->popValue(this), "__enter__"_c); break; + case OP_WITH_EXIT: call(frame->popValue(this), "__exit__"_c); break; default: systemError(_Str("opcode ") + OP_NAMES[byte.op] + " is not implemented"); break; @@ -404,7 +397,7 @@ public: PyVar asStr(const PyVar& obj){ PyVarOrNull str_fn = getAttr(obj, __str__, false); - if(str_fn != nullptr) return call(str_fn, {}); + if(str_fn != nullptr) return call(str_fn); return asRepr(obj); } @@ -424,11 +417,11 @@ public: PyVar asRepr(const PyVar& obj){ if(obj->isType(_tp_type)) return PyStr("attribs[__name__]) + "'>"); - return call(obj, __repr__, {}); + return call(obj, __repr__); } PyVar asJson(const PyVar& obj){ - return call(obj, __json__, {}); + return call(obj, __json__); } const PyVar& asBool(const PyVar& obj){ @@ -438,7 +431,7 @@ public: if(obj->_type == _tp_float) return PyBool(PyFloat_AS_C(obj) != 0.0); PyVarOrNull len_fn = getAttr(obj, __len__, false); if(len_fn != nullptr){ - PyVar ret = call(std::move(len_fn), {}); + PyVar ret = call(len_fn); return PyBool(PyInt_AS_C(ret) > 0); } return True; @@ -449,25 +442,39 @@ public: PyObject* cls = obj->_type.get(); while(cls != None.get()) { auto it = cls->attribs.find(name); - if(it != cls->attribs.end()){ - return call(it->second, args); - } + if(it != cls->attribs.end()) return call(it->second, std::move(args)); cls = cls->attribs[__base__].get(); } attributeError(obj, name); return nullptr; } - PyVar call(const PyVar& _callable, pkpy::ArgList args, bool opCall=false){ + inline PyVar call(const PyVar& _callable){ + return call(_callable, pkpy::noArg(), pkpy::noArg(), false); + } + + inline PyVar call(const PyVar& _callable, pkpy::ArgList args){ + return call(_callable, args, pkpy::noArg(), false); + } + + inline PyVar call(const PyVar& obj, const _Str& func, pkpy::ArgList args){ + return call(getAttr(obj, func), args, pkpy::noArg(), false); + } + + inline PyVar call(const PyVar& obj, const _Str& func){ + return call(getAttr(obj, func), pkpy::noArg(), pkpy::noArg(), false); + } + + PyVar call(const PyVar& _callable, pkpy::ArgList args, const pkpy::ArgList& kwargs, bool opCall){ if(_callable->isType(_tp_type)){ auto it = _callable->attribs.find(__new__); PyVar obj; if(it != _callable->attribs.end()){ - obj = call(it->second, args); + obj = call(it->second, args, kwargs, false); }else{ obj = newObject(_callable, (_Int)-1); PyVarOrNull init_fn = getAttr(obj, __init__, false); - if (init_fn != nullptr) call(init_fn, args); + if (init_fn != nullptr) call(init_fn, args, kwargs, false); } return obj; } @@ -485,54 +492,67 @@ public: if((*callable)->isType(_tp_native_function)){ const auto& f = UNION_GET(_CppFunc, *callable); + // _CppFunc do not support kwargs return f(this, args); } else if((*callable)->isType(_tp_function)){ const _Func& fn = PyFunction_AS_C((*callable)); PyVarDict locals; int i = 0; for(const auto& name : fn->args){ - if(i < args.size()) { - locals[name] = args[i++]; - }else{ - typeError("missing positional argument '" + name + "'"); - } - } - // handle *args - if(!fn->starredArg.empty()){ - PyVarList vargs; - while(i < args.size()) vargs.push_back(args[i++]); - locals[fn->starredArg] = PyTuple(vargs); - } - // handle keyword arguments - for(const _Str& name : fn->kwArgsOrder){ - if(i < args.size()) { - locals[name] = args[i++]; - }else{ - locals[name] = fn->kwArgs[name]; + if(i < args.size()){ + locals.emplace(name, args[i++]); + continue; } + typeError("missing positional argument '" + name + "'"); } - if(i < args.size()) typeError("too many arguments"); + locals.insert(fn->kwArgs.begin(), fn->kwArgs.end()); + + std::vector<_Str> positional_overrided_keys; + if(!fn->starredArg.empty()){ + // handle *args + PyVarList vargs; + while(i < args.size()) vargs.push_back(args[i++]); + locals.emplace(fn->starredArg, PyTuple(std::move(vargs))); + }else{ + for(const auto& key : fn->kwArgsOrder){ + if(i < args.size()){ + locals[key] = args[i++]; + positional_overrided_keys.push_back(key); + }else{ + break; + } + } + if(i < args.size()) typeError("too many arguments"); + } + + for(int i=0; ikwArgs.find(key) == fn->kwArgs.end()){ + typeError(key.__escape(true) + " is an invalid keyword argument for " + fn->name + "()"); + } + const PyVar& val = kwargs[i+1]; + if(!positional_overrided_keys.empty()){ + auto it = std::find(positional_overrided_keys.begin(), positional_overrided_keys.end(), key); + if(it != positional_overrided_keys.end()){ + typeError("multiple values for argument '" + key + "'"); + } + } + locals[key] = val; + } auto it_m = (*callable)->attribs.find(__module__); PyVar _module = it_m != (*callable)->attribs.end() ? it_m->second : topFrame()->_module; if(opCall){ - __pushNewFrame(fn->code, _module, locals); + __pushNewFrame(fn->code, _module, std::move(locals)); return __py2py_call_signal; } - return _exec(fn->code, _module, locals); + return _exec(fn->code, _module, std::move(locals)); } typeError("'" + UNION_TP_NAME(*callable) + "' object is not callable"); return None; } - inline PyVar call(const PyVar& obj, const _Str& func, const pkpy::ArgList& args){ - return call(getAttr(obj, func), args); - } - - inline PyVar call(const PyVar& obj, const _Str& func, pkpy::ArgList&& args){ - return call(getAttr(obj, func), args); - } // repl mode is only for setting `frame->id` to 0 virtual PyVarOrNull exec(const _Code& code, PyVar _module=nullptr){ @@ -552,18 +572,18 @@ public: exec(code); } - Frame* __pushNewFrame(const _Code& code, PyVar _module, const PyVarDict& locals){ + Frame* __pushNewFrame(const _Code& code, PyVar _module, PyVarDict&& locals){ if(code == nullptr) UNREACHABLE(); if(callstack.size() > maxRecursionDepth){ throw RuntimeError("RecursionError", "maximum recursion depth exceeded", _cleanErrorAndGetSnapshots()); } - Frame* frame = new Frame(code.get(), _module, locals); + Frame* frame = new Frame(code.get(), _module, std::move(locals)); callstack.emplace_back(pkpy::unique_ptr(frame)); return frame; } - PyVar _exec(const _Code& code, PyVar _module, const PyVarDict& locals){ - Frame* frame = __pushNewFrame(code, _module, locals); + PyVar _exec(const _Code& code, PyVar _module, PyVarDict&& locals){ + Frame* frame = __pushNewFrame(code, _module, std::move(locals)); if(code->mode() == SINGLE_MODE) frame->id = 0; Frame* frameBase = frame; PyVar ret = nullptr; @@ -710,10 +730,10 @@ public: bool isInstance(PyVar obj, PyVar type){ __checkType(type, _tp_type); - PyVar t = obj->_type; - while (t != None){ - if (t == type) return true; - t = t->attribs[__base__]; + PyObject* t = obj->_type.get(); + while (t != None.get()){ + if (t == type.get()) return true; + t = t->attribs[__base__].get(); } return false; } @@ -1008,15 +1028,15 @@ void AttrPointer::del(VM* vm, Frame* frame) const{ } PyVar IndexPointer::get(VM* vm, Frame* frame) const{ - return vm->call(obj, __getitem__, {index}); + return vm->call(obj, __getitem__, pkpy::oneArg(index)); } void IndexPointer::set(VM* vm, Frame* frame, PyVar val) const{ - vm->call(obj, __setitem__, {index, val}); + vm->call(obj, __setitem__, pkpy::twoArgs(index, val)); } void IndexPointer::del(VM* vm, Frame* frame) const{ - vm->call(obj, __delitem__, {index}); + vm->call(obj, __delitem__, pkpy::oneArg(index)); } PyVar CompoundPointer::get(VM* vm, Frame* frame) const{ diff --git a/tests/functions.py b/tests/functions.py index 37fdcd8a..9989c488 100644 --- a/tests/functions.py +++ b/tests/functions.py @@ -17,3 +17,24 @@ def fact(n): return n * fact(n - 1) assert fact(5)==120 +def f(a=1, b=-1): + return a + b + +assert f() == 0 +assert f(1, 2) == 3 +assert f(-5) == -6 +assert f(b=5) == 6 +assert f(a=5) == 4 +assert f(b=5, a=5) == 10 + +def f(a, b, *c, d=2, e=5): + return a + b + d + e + sum(c) + +assert f(1, 2, 3, 4, 5, 6, 7, 8, 9, 10) == 62 +assert f(1, 2, 3, 4, 5, 6, 7, 8, 9, 10, d=1, e=2) == 58 +assert f(1, 2, 3, 4, 5, 6, 7, 8, 9, 10, e=1, d=2) == 58 +assert f(1, 2, 3, 4, 5, 6, 7, 8, 9, 10, d=1) == 61 +assert f(1, 2, 3, 4, 5, 6, 7, 8, 9, 10, e=1) == 58 +assert f(1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20) == 217 +assert f(1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, d=1, e=2) == 213 +