Browse Source

Merge branch 'main' into c_binding_api

Kolten Pearson 2 years ago
parent
commit
6bc1749b5c
9 changed files with 168 additions and 21 deletions
  1. 1 0
      .gitignore
  2. 1 1
      amalgamate.py
  3. 1 1
      python/_dict.py
  4. 40 0
      python/requests.py
  5. 4 9
      src/io.h
  6. 8 4
      src/obj.h
  7. 7 5
      src/pocketpy.h
  8. 103 0
      src/requests.h
  9. 3 1
      src/vm.h

+ 1 - 0
.gitignore

@@ -24,3 +24,4 @@ src/_generated.h
 profile.sh
 test
 tmp.rar
+src/httplib.h

+ 1 - 1
amalgamate.py

@@ -9,7 +9,7 @@ pipeline = [
 	["common.h", "memory.h", "vector.h", "str.h", "tuplelist.h", "namedict.h", "error.h", "lexer.h"],
 	["obj.h", "codeobject.h", "frame.h"],
 	["gc.h", "vm.h", "ceval.h", "expr.h", "compiler.h", "repl.h"],
-	["iter.h", "cffi.h", "io.h", "_generated.h", "pocketpy.h"]
+	["_generated.h", "iter.h", "cffi.h", "requests.h", "io.h", "pocketpy.h"]
 ]
 
 copied = set()

+ 1 - 1
python/_dict.py

@@ -74,7 +74,7 @@ class dict:
     def items(self):
         for kv in self._a:
             if kv is not None:
-                yield kv
+                yield kv[0], kv[1]
 
     def clear(self):
         self._a = [None] * self._capacity

+ 40 - 0
python/requests.py

@@ -0,0 +1,40 @@
+class Response:
+    def __init__(self, status_code, reason, content):
+        self.status_code = status_code
+        self.reason = reason
+        self.content = content
+
+        assert type(self.status_code) is int
+        assert type(self.reason) is str
+        assert type(self.content) is bytes
+
+    @property
+    def text(self):
+        return self.content.decode()
+    
+    def __repr__(self):
+        code = self.status_code
+        return f'<Response [{code}]>'
+
+def _parse_h(headers):
+    if headers is None:
+        return []
+    if type(headers) is dict:
+        return list(headers.items())
+    raise ValueError('headers must be dict or None')
+
+def get(url, headers=None):
+    headers = _parse_h(headers)
+    return _request('GET', url, headers, None)
+
+def post(url, data: bytes, headers=None):
+    headers = _parse_h(headers)
+    return _request('POST', url, headers, data)
+
+def put(url, data: bytes, headers=None):
+    headers = _parse_h(headers)
+    return _request('PUT', url, headers, data)
+
+def delete(url, headers=None):
+    headers = _parse_h(headers)
+    return _request('DELETE', url, headers, None)

+ 4 - 9
src/io.h

@@ -11,7 +11,7 @@
 
 namespace pkpy{
 
-inline Bytes _read_file_cwd(const Str& name){
+inline int _ = set_read_file_cwd([](const Str& name){
     std::filesystem::path path(name.sv());
     bool exists = std::filesystem::exists(path);
     if(!exists) return Bytes();
@@ -19,7 +19,7 @@ inline Bytes _read_file_cwd(const Str& name){
     std::vector<char> buffer(std::istreambuf_iterator<char>(ifs), {});
     ifs.close();
     return Bytes(std::move(buffer));
-}
+});
 
 struct FileIO {
     PY_CLASS(FileIO, io, FileIO)
@@ -182,13 +182,8 @@ inline void add_module_os(VM* vm){
 #else
 
 namespace pkpy{
-inline void add_module_io(VM* vm){}
-inline void add_module_os(VM* vm){}
-
-inline Bytes _read_file_cwd(const Str& name){
-    return Bytes();
-}
-
+inline void add_module_io(void* vm){}
+inline void add_module_os(void* vm){}
 } // namespace pkpy
 
 #endif

+ 8 - 4
src/obj.h

@@ -12,19 +12,22 @@ struct Function;
 class VM;
 
 typedef PyObject* (*NativeFuncC)(VM*, ArgsView);
-typedef shared_ptr<CodeObject> CodeObject_;
+typedef int (*LuaStyleFuncC)(VM*);
 
 struct NativeFunc {
     NativeFuncC f;
     int argc;       // DONOT include self
     bool method;
+
+    // this is designed for lua style C bindings
+    // access it via `CAST(NativeFunc&, args[-2])._lua_f`
+    LuaStyleFuncC _lua_f;
     
-    NativeFunc(NativeFuncC f, int argc, bool method) : f(f), argc(argc), method(method) {}
+    NativeFunc(NativeFuncC f, int argc, bool method) : f(f), argc(argc), method(method), _lua_f(nullptr) {}
     PyObject* operator()(VM* vm, ArgsView args) const;
 };
 
-
-typedef void (*StackFunc)(VM*);
+typedef shared_ptr<CodeObject> CodeObject_;
 
 struct FuncDecl {
     struct KwArg {
@@ -79,6 +82,7 @@ struct Bytes{
 
     Bytes() : _data(), _ok(false) {}
     Bytes(std::vector<char>&& data) : _data(std::move(data)), _ok(true) {}
+    Bytes(const std::string& data) : _data(data.begin(), data.end()), _ok(true) {}
     operator bool() const noexcept { return _ok; }
 };
 

+ 7 - 5
src/pocketpy.h

@@ -6,6 +6,7 @@
 #include "repl.h"
 #include "iter.h"
 #include "cffi.h"
+#include "requests.h"
 #include "io.h"
 #include "_generated.h"
 
@@ -920,11 +921,6 @@ inline void VM::post_init(){
     add_module_gc(this);
     add_module_random(this);
 
-    if(enable_os){
-        add_module_io(this);
-        add_module_os(this);
-    }
-
     for(const char* name: {"this", "functools", "collections", "heapq", "bisect"}){
         _lazy_modules[name] = kPythonLibs[name];
     }
@@ -969,6 +965,12 @@ inline void VM::post_init(){
         }
         return VAR(MappingProxy(args[0]));
     }));
+
+    if(enable_os){
+        add_module_io(this);
+        add_module_os(this);
+        add_module_requests(this);
+    }
 #endif
 }
 

+ 103 - 0
src/requests.h

@@ -0,0 +1,103 @@
+#pragma once
+
+#include "common.h"
+#include "obj.h"
+#include "vm.h"
+#include "_generated.h"
+
+#if __has_include("httplib.h")
+#include "httplib.h"
+
+namespace pkpy {
+
+inline void add_module_requests(VM* vm){
+    static StrName m_requests("requests");
+    static StrName m_Response("Response");
+    PyObject* mod = vm->new_module(m_requests);
+    CodeObject_ code = vm->compile(kPythonLibs["requests"], "requests.py", EXEC_MODE);
+    vm->_exec(code, mod);
+
+    vm->bind_func<4>(mod, "_request", [](VM* vm, ArgsView args){
+        Str method = CAST(Str&, args[0]);
+        Str url = CAST(Str&, args[1]);
+        PyObject* headers = args[2];            // a dict object
+        PyObject* body = args[3];               // a bytes object
+
+        if(url.index("http://") != 0){
+            vm->ValueError("url must start with http://");
+        }
+
+        for(char c: url){
+            switch(c){
+                case '.':
+                case '-':
+                case '_':
+                case '~':
+                case ':':
+                case '/':
+                break;
+                default:
+                    if(!isalnum(c)){
+                        vm->ValueError(fmt("invalid character in url: '", c, "'"));
+                    }
+            }
+        }
+
+        int slash = url.index("/", 7);
+        Str path = "/";
+        if(slash != -1){
+            path = url.substr(slash);
+            url = url.substr(0, slash);
+            if(path.empty()) path = "/";
+        }
+
+        httplib::Client client(url.str());
+
+        httplib::Headers h;
+        if(headers != vm->None){
+            List list = CAST(List&, headers);
+            for(auto& item : list){
+                Tuple t = CAST(Tuple&, item);
+                Str key = CAST(Str&, t[0]);
+                Str value = CAST(Str&, t[1]);
+                h.emplace(key.str(), value.str());
+            }
+        }
+
+        auto _to_resp = [=](const httplib::Result& res){
+            return vm->call(
+                vm->_modules[m_requests]->attr(m_Response),
+                VAR(res->status),
+                VAR(res->reason),
+                VAR(Bytes(res->body))
+            );
+        };
+
+        if(method == "GET"){
+            httplib::Result res = client.Get(path.str(), h);
+            return _to_resp(res);
+        }else if(method == "POST"){
+            Bytes b = CAST(Bytes&, body);
+            httplib::Result res = client.Post(path.str(), h, b.data(), b.size(), "application/octet-stream");
+            return _to_resp(res);
+        }else if(method == "PUT"){
+            Bytes b = CAST(Bytes&, body);
+            httplib::Result res = client.Put(path.str(), h, b.data(), b.size(), "application/octet-stream");
+            return _to_resp(res);
+        }else if(method == "DELETE"){
+            httplib::Result res = client.Delete(path.str(), h);
+            return _to_resp(res);
+        }else{
+            vm->ValueError("invalid method");
+        }
+        UNREACHABLE();
+    });
+}
+
+}   // namespace pkpy
+
+#else
+
+inline void add_module_requests(void* vm){ }
+
+#endif

+ 3 - 1
src/vm.h

@@ -24,7 +24,9 @@ namespace pkpy{
 #define POPX()            (s_data.popx())
 #define STACK_VIEW(n)     (s_data.view(n))
 
-Bytes _read_file_cwd(const Str& name);
+typedef Bytes (*ReadFileCwdFunc)(const Str& name);
+inline ReadFileCwdFunc _read_file_cwd = [](const Str& name) { return Bytes(); };
+inline int set_read_file_cwd(ReadFileCwdFunc func) { _read_file_cwd = func; return 0; }
 
 #define DEF_NATIVE_2(ctype, ptype)                                      \
     template<> inline ctype py_cast<ctype>(VM* vm, PyObject* obj) {     \