From 9c16aefda3f47fc7a1fe87d03f259c1d6d65abf5 Mon Sep 17 00:00:00 2001 From: blueloveTH Date: Sat, 3 Dec 2022 19:32:24 +0800 Subject: [PATCH] add json --- src/compiler.h | 21 +++++++--- src/error.h | 3 +- src/main.cpp | 6 +-- src/parser.h | 14 +++++++ src/pocketpy.h | 110 +++++++++++++++++++++++-------------------------- src/vm.h | 4 +- tests/json.py | 43 +++++++++++++++++++ 7 files changed, 132 insertions(+), 69 deletions(-) create mode 100644 tests/json.py diff --git a/src/compiler.h b/src/compiler.h index 363ecbe9..92bc5880 100644 --- a/src/compiler.h +++ b/src/compiler.h @@ -905,11 +905,15 @@ __LISTCOMP: { case 0: func->args.push_back(name); break; case 1: func->starredArg = name; state+=1; break; - case 2: + case 2: { consume(TK("=")); - func->kwArgs[name] = consumeLiteral(); + PyVarOrNull value = readLiteral(); + if(value == nullptr){ + syntaxError(_Str("expect a literal, not ") + TK_STR(parser->current.type)); + } + func->kwArgs[name] = value; func->kwArgsOrder.push_back(name); - break; + } break; case 3: syntaxError("**kwargs is not supported yet"); break; } } while (match(TK(","))); @@ -937,7 +941,7 @@ __LISTCOMP: if(!isCompilingClass) emitCode(OP_STORE_FUNCTION); } - PyVar consumeLiteral(){ + PyVarOrNull readLiteral(){ if(match(TK("-"))){ consume(TK("@num")); PyVar val = parser->previous.value; @@ -949,7 +953,6 @@ __LISTCOMP: if(match(TK("False"))) return vm->PyBool(false); if(match(TK("None"))) return vm->None; if(match(TK("..."))) return vm->Ellipsis; - syntaxError(_Str("expect a literal, not ") + TK_STR(parser->current.type)); return nullptr; } @@ -980,6 +983,14 @@ __LISTCOMP: EXPR_TUPLE(); consume(TK("@eof")); return code; + }else if(mode()==JSON_MODE){ + PyVarOrNull value = readLiteral(); + if(value != nullptr) emitCode(OP_LOAD_CONST, code->addConst(value)); + else if(match(TK("{"))) exprMap(); + else if(match(TK("["))) exprList(); + else syntaxError("expect a JSON object or array"); + consume(TK("@eof")); + return code; } while (!match(TK("@eof"))) { diff --git a/src/error.h b/src/error.h index 2ac58fcc..c436d14f 100644 --- a/src/error.h +++ b/src/error.h @@ -11,7 +11,8 @@ public: enum CompileMode { EXEC_MODE, EVAL_MODE, - SINGLE_MODE // for REPL + SINGLE_MODE, // for REPL + JSON_MODE, }; struct SourceMetadata { diff --git a/src/main.cpp b/src/main.cpp index 142b2ff9..eda9e4ba 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -42,15 +42,15 @@ extern "C" { void _tvm_dispatch(ThreadedVM* vm){ while(pkpy_tvm_get_state(vm) != THREAD_FINISHED){ if(pkpy_tvm_get_state(vm) == THREAD_SUSPENDED){ - PyObjectDump* obj = pkpy_tvm_read_jsonrpc_request(vm); - bool is_input_call = INPUT_JSONRPC_STR == obj->json; + char* obj = pkpy_tvm_read_jsonrpc_request(vm); + bool is_input_call = INPUT_JSONRPC_STR == std::string(obj); if(is_input_call){ std::string line; std::getline(std::cin, line); pkpy_tvm_resume(vm, line.c_str()); }else{ std::cout << "unknown jsonrpc call" << std::endl; - std::cout << obj->json << std::endl; + std::cout << obj << std::endl; exit(3); } pkpy_delete(obj); diff --git a/src/parser.h b/src/parser.h index 82c64e5a..8fac9cfd 100644 --- a/src/parser.h +++ b/src/parser.h @@ -222,6 +222,20 @@ struct Parser { int length = (int)(current_char - token_start); if(length == 0) return 3; std::string_view name(token_start, length); + + if(src->mode == JSON_MODE){ + if(name == "true"){ + setNextToken(TK("True")); + } else if(name == "false"){ + setNextToken(TK("False")); + } else if(name == "null"){ + setNextToken(TK("None")); + } else { + return 4; + } + return 0; + } + if(__KW_MAP.count(name)){ if(name == "not"){ if(strncmp(current_char, " in", 3) == 0){ diff --git a/src/pocketpy.h b/src/pocketpy.h index f95022fe..1823a804 100644 --- a/src/pocketpy.h +++ b/src/pocketpy.h @@ -161,6 +161,10 @@ void __initializeBuiltinFunctions(VM* _vm) { return vm->PyStr("null"); }); + _vm->bindMethod("NoneType", "__eq__", [](VM* vm, const pkpy::ArgList& args) { + return vm->PyBool(args[0] == args[1]); + }); + _vm->bindMethodMulti({"int", "float"}, "__truediv__", [](VM* vm, const pkpy::ArgList& args) { if(!vm->isIntOrFloat(args[0], args[1])) vm->typeError("unsupported operand type(s) for " "/" ); @@ -630,6 +634,22 @@ void __addModuleSys(VM* vm){ vm->setAttr(mod, "version", vm->PyStr(PK_VERSION)); } +void __addModuleJson(VM* vm){ + PyVar mod = vm->newModule("json"); + vm->bindFunc(mod, "loads", [](VM* vm, const pkpy::ArgList& args) { + vm->__checkArgSize(args, 1); + const _Str& expr = vm->PyStr_AS_C(args[0]); + _Code code = compile(vm, expr.c_str(), "", JSON_MODE); + if(code == nullptr) return vm->None; + return vm->_exec(code, vm->topFrame()->_module, vm->topFrame()->f_locals); + }); + + vm->bindFunc(mod, "dumps", [](VM* vm, const pkpy::ArgList& args) { + vm->__checkArgSize(args, 1); + return vm->asJson(args[0]); + }); +} + class _PkExported; static std::vector<_PkExported*> _pkLookupTable; class _PkExported{ @@ -643,7 +663,8 @@ class PkExported : public _PkExported{ T* _ptr; public: template - PkExported(Args&&... args) : _ptr(new T(std::forward(args)...)){ + PkExported(Args&&... args) { + _ptr = new T(std::forward(args)...); _pkLookupTable.push_back(this); } @@ -656,91 +677,60 @@ public: extern "C" { - struct PyObjectDump { - const char* type; // "int", "str", "float" ... - const char* json; // json representation - - PyObjectDump(_Str _type, _Str _json){ - type = strdup(_type.c_str()); - json = strdup(_json.c_str()); - } - - ~PyObjectDump(){ - delete[] type; - delete[] json; - } - }; - - struct PyOutputDump { - const char* _stdout; - const char* _stderr; - - PyOutputDump(_Str _stdout, _Str _stderr){ - this->_stdout = strdup(_stdout.c_str()); - this->_stderr = strdup(_stderr.c_str()); - } - - ~PyOutputDump(){ - delete[] _stdout; - delete[] _stderr; - } - }; - __EXPORT - /// Delete a pointer allocated by `pkpy_xxx_xxx`. - /// It can be `VM*`, `REPL*` or `PyXXXDump*`, etc. + /// Delete a class pointer allocated by `pkpy_xxx_xxx`. + /// It can be `VM*`, `REPL*`, `ThreadedVM*`, `char*`, etc. /// /// !!! - /// If the pointer is not allocated by `pkpy_xxx_xxx`, nothing will happen. + /// If the pointer is not allocated by `pkpy_xxx_xxx`, the behavior is undefined. + /// For char*, you can also use trivial `delete` in your language. /// !!! void pkpy_delete(void* p){ for(int i = 0; i < _pkLookupTable.size(); i++){ if(_pkLookupTable[i]->get() == p){ delete _pkLookupTable[i]; _pkLookupTable.erase(_pkLookupTable.begin() + i); + return; } } + free(p); } __EXPORT /// Run a given source on a virtual machine. /// - /// Return `true` if there is no error. + /// Return `true` if there is no compile error. bool pkpy_vm_exec(VM* vm, const char* source){ _Code code = compile(vm, source, "main.py"); if(code == nullptr) return false; - return vm->exec(code) != nullptr; + vm->exec(code); + return true; } __EXPORT /// Get a global variable of a virtual machine. - /// Return a `PyObjectDump*` representing the variable. - /// You need to call `pkpy_delete` to free the returned `PyObjectDump*` later. + /// + /// Return a json representing the result. /// If the variable is not found, return `nullptr`. - PyObjectDump* pkpy_vm_get_global(VM* vm, const char* name){ + char* pkpy_vm_get_global(VM* vm, const char* name){ auto it = vm->_main->attribs.find(name); if(it == vm->_main->attribs.end()) return nullptr; - return pkpy_allocate(PyObjectDump, - it->second->getTypeName().c_str(), - vm->PyStr_AS_C(vm->asJson(it->second)).c_str() - ); + _Str _json = vm->PyStr_AS_C(vm->asJson(it->second)); + return strdup(_json.c_str()); } __EXPORT /// Evaluate an expression. /// - /// Return a `PyObjectDump*` representing the result. - /// You need to call `pkpy_delete` to free the returned `PyObjectDump*` later. + /// Return a json representing the result. /// If there is any error, return `nullptr`. - PyObjectDump* pkpy_vm_eval(VM* vm, const char* source){ + char* pkpy_vm_eval(VM* vm, const char* source){ _Code code = compile(vm, source, "", EVAL_MODE); if(code == nullptr) return nullptr; PyVarOrNull ret = vm->exec(code); if(ret == nullptr) return nullptr; - return pkpy_allocate(PyObjectDump, - ret->getTypeName(), - vm->PyStr_AS_C(vm->asJson(ret)) - ); + _Str _json = vm->PyStr_AS_C(vm->asJson(ret)); + return strdup(_json.c_str()); } __EXPORT @@ -779,6 +769,7 @@ extern "C" { __addModuleSys(vm); __addModuleTime(vm); + __addModuleJson(vm); pkpy_vm_add_module(vm, "random", __RANDOM_CODE); } @@ -803,17 +794,20 @@ extern "C" { /// The `vm->use_stdio` should be `false`. /// After this operation, both stream will be cleared. /// - /// Return a `PyOutputDump*` representing the result. - /// You need to call `pkpy_delete` to free the returned `PyOutputDump*` later. - PyOutputDump* pkpy_vm_read_output(VM* vm){ + /// Return a json representing the result. + char* pkpy_vm_read_output(VM* vm){ if(vm->use_stdio) return nullptr; _StrStream* s_out = dynamic_cast<_StrStream*>(vm->_stdout); _StrStream* s_err = dynamic_cast<_StrStream*>(vm->_stderr); - if(s_out == nullptr || s_err == nullptr) return nullptr; - PyOutputDump* dump = pkpy_allocate(PyOutputDump, s_out->str(), s_err->str()); + _Str _stdout = s_out->str(); + _Str _stderr = s_err->str(); + _StrStream ss; + ss << '{' << "\"stdout\": " << _stdout.__escape(false); + ss << ", "; + ss << "\"stderr\": " << _stderr.__escape(false) << '}'; s_out->str(""); s_err->str(""); - return dump; + return strdup(ss.str().c_str()); } __EXPORT @@ -840,10 +834,10 @@ extern "C" { /// Return a `PyObjectDump*` representing the string. /// You need to call `pkpy_delete` to free the returned `PyObjectDump*` later. /// If the buffer is empty, return `nullptr`. - PyObjectDump* pkpy_tvm_read_jsonrpc_request(ThreadedVM* vm){ + char* pkpy_tvm_read_jsonrpc_request(ThreadedVM* vm){ std::optional<_Str> s = vm->readSharedStr(); if(!s.has_value()) return nullptr; - return pkpy_allocate(PyObjectDump, "str"_c, s.value()); + return strdup(s.value().c_str()); } __EXPORT diff --git a/src/vm.h b/src/vm.h index 75d0e0a6..e67cd7d9 100644 --- a/src/vm.h +++ b/src/vm.h @@ -352,8 +352,8 @@ protected: } } - if(frame->code->src->mode == EVAL_MODE) { - if(frame->stackSize() != 1) systemError("stack size is not 1 in EVAL_MODE"); + if(frame->code->src->mode == EVAL_MODE || frame->code->src->mode == JSON_MODE){ + if(frame->stackSize() != 1) systemError("stack size is not 1 in EVAL_MODE/JSON_MODE"); return frame->popValue(this); } diff --git a/tests/json.py b/tests/json.py new file mode 100644 index 00000000..b285faed --- /dev/null +++ b/tests/json.py @@ -0,0 +1,43 @@ +a = { + 'a': 1, + 'b': 2, + 'c': None, + 'd': [1, 2, 3], + # 'e': { + # 'a': 1, + # 'b': 2, + # 'c': None, + # 'd': [1, 2, 3], + # }, + "f": 'This is a string', + 'g': [True, False, None], + 'h': False +} + +import json + +_j = json.dumps(a) +_a = json.loads(_j) + +for k, v in a.items(): + assert a[k] == _a[k] + +for k, v in _a.items(): + assert a[k] == _a[k] + +b = [1, 2, True, None, False] + +_j = json.dumps(b) +_b = json.loads(_j) + +assert b == _b + +c = 1.0 +_j = json.dumps(c) +_c = json.loads(_j) +assert c == _c + +d = True +_j = json.dumps(d) +_d = json.loads(_j) +assert d == _d \ No newline at end of file