blueloveTH 1 год назад
Родитель
Сommit
ea9dabdf99

+ 1 - 1
include/pocketpy/common/config.h

@@ -10,7 +10,7 @@
 
 // Whether to compile os-related modules or not
 #ifndef PK_ENABLE_OS                // can be overridden by cmake
-#define PK_ENABLE_OS                0
+#define PK_ENABLE_OS                1
 #endif
 
 // Enable `line_profiler` module and `breakpoint()` function

+ 2 - 0
include/pocketpy/common/str.h

@@ -49,6 +49,8 @@ int c11_sv__index(c11_sv self, char c);
 int c11_sv__rindex(c11_sv self, char c);
 int c11_sv__index2(c11_sv self, c11_sv sub, int start);
 int c11_sv__count(c11_sv self, c11_sv sub);
+bool c11_sv__startswith(c11_sv self, c11_sv prefix);
+bool c11_sv__endswith(c11_sv self, c11_sv suffix);
 
 c11_string* c11_sv__replace(c11_sv self, char old, char new_);
 c11_string* c11_sv__replace2(c11_sv self, c11_sv old, c11_sv new_);

+ 13 - 0
include/pocketpy/interpreter/modules.h

@@ -0,0 +1,13 @@
+#pragma once
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+void pk__add_module_pkpy();
+void pk__add_module_os();
+void pk__add_module_math();
+
+#ifdef __cplusplus
+}
+#endif

+ 1 - 2
include/pocketpy/interpreter/vm.h

@@ -4,6 +4,7 @@
 #include "pocketpy/pocketpy.h"
 #include "pocketpy/interpreter/gc.h"
 #include "pocketpy/interpreter/frame.h"
+#include "pocketpy/interpreter/modules.h"
 
 #ifdef __cplusplus
 extern "C" {
@@ -124,8 +125,6 @@ py_Type pk_Exception__register();
 
 py_TValue pk_builtins__register();
 
-void pk__add_module_pkpy();
-
 #ifdef __cplusplus
 }
 #endif

+ 10 - 2
include/pocketpy/pocketpy.h

@@ -7,6 +7,10 @@
 #include "pocketpy/common/config.h"
 #include "pocketpy/common/export.h"
 
+#ifdef __cplusplus
+extern "C" {
+#endif
+
 /************* Public Types *************/
 typedef struct py_TValue py_TValue;
 typedef uint16_t py_Name;
@@ -121,7 +125,7 @@ void* py_newobject(py_Ref out, py_Type type, int slots, int udsize);
 /************* Type Cast *************/
 py_i64 py_toint(py_Ref);
 py_f64 py_tofloat(py_Ref);
-bool py_castfloat(py_Ref, py_f64* out);
+bool py_castfloat(py_Ref, py_f64* out) PY_RAISE;
 bool py_tobool(py_Ref);
 py_Type py_totype(py_Ref);
 const char* py_tostr(py_Ref);
@@ -186,6 +190,7 @@ py_GlobalRef py_retval();
 py_ObjectRef py_getdict(py_Ref self, py_Name name);
 void py_setdict(py_Ref self, py_Name name, py_Ref val);
 bool py_deldict(py_Ref self, py_Name name);
+py_ObjectRef py_emplacedict(py_Ref self, py_Name name);
 
 /// Get the reference of the i-th slot of the object.
 /// The object must have slots and `i` must be in range.
@@ -264,7 +269,8 @@ py_TmpRef py_getmodule(const char* path);
 
 /// Import a module.
 /// The result will be set to `py_retval()`.
-bool py_import(const char* path) PY_RAISE;
+/// -1: error, 0: not found, 1: success
+int py_import(const char* path) PY_RAISE;
 
 /************* Errors *************/
 /// Raise an exception by name and message. Always returns false.
@@ -280,6 +286,8 @@ bool py_checkexc();
 /// Clear the current exception.
 void py_clearexc(py_StackRef p0);
 
+#define IOError(...) py_exception("IOError", __VA_ARGS__)
+#define OSError(...) py_exception("OSError", __VA_ARGS__)
 #define NameError(n) py_exception("NameError", "name '%n' is not defined", (n))
 #define TypeError(...) py_exception("TypeError", __VA_ARGS__)
 #define RuntimeError(...) py_exception("RuntimeError", __VA_ARGS__)

+ 0 - 1
include/pocketpy/xmacros/magics.h

@@ -56,7 +56,6 @@ MAGIC_METHOD(__exit__)
 MAGIC_METHOD(__name__)
 MAGIC_METHOD(__all__)
 MAGIC_METHOD(__package__)
-MAGIC_METHOD(__module_is_pending__)
 MAGIC_METHOD(__path__)
 MAGIC_METHOD(__class__)
 MAGIC_METHOD(__abs__)

+ 10 - 0
src/common/str.c

@@ -165,6 +165,16 @@ int c11_sv__count(c11_sv self, c11_sv sub) {
     return cnt;
 }
 
+bool c11_sv__startswith(c11_sv self, c11_sv prefix) {
+    if(prefix.size > self.size) return false;
+    return memcmp(self.data, prefix.data, prefix.size) == 0;
+}
+
+bool c11_sv__endswith(c11_sv self, c11_sv suffix) {
+    if(suffix.size > self.size) return false;
+    return memcmp(self.data + self.size - suffix.size, suffix.data, suffix.size) == 0;
+}
+
 c11_vector /* T=c11_sv */ c11_sv__split(c11_sv self, char sep) {
     c11_vector retval;
     c11_vector__ctor(&retval, sizeof(c11_sv));

+ 8 - 2
src/interpreter/ceval.c

@@ -257,6 +257,7 @@ pk_FrameResult pk_VM__run_top_frame(pk_VM* self) {
                     // fallback to getattr
                     if(py_getattr(TOP(), byte.arg)) {
                         py_assign(TOP(), py_retval());
+                        py_newnil(SP()++);
                     } else {
                         goto __ERROR;
                     }
@@ -757,8 +758,13 @@ pk_FrameResult pk_VM__run_top_frame(pk_VM* self) {
             ////////
             case OP_IMPORT_PATH: {
                 py_Ref path_object = c11__at(py_TValue, &frame->co->consts, byte.arg);
-                bool ok = py_import(py_tostr(path_object));
-                if(!ok) goto __ERROR;
+                const char* path = py_tostr(path_object);
+                int res = py_import(path);
+                if(res == -1) goto __ERROR;
+                if(res == 0) {
+                    ImportError("module '%s' not found", path);
+                    goto __ERROR;
+                }
                 PUSH(py_retval());
                 DISPATCH();
             }

+ 25 - 2
src/interpreter/vm.c

@@ -6,7 +6,22 @@
 #include "pocketpy/common/_generated.h"
 #include "pocketpy/pocketpy.h"
 
-static char* pk_default_import_file(const char* path) { return NULL; }
+static char* pk_default_import_file(const char* path) {
+#if PK_ENABLE_OS
+    FILE* f = fopen(path, "rb");
+    if(f == NULL) return NULL;
+    fseek(f, 0, SEEK_END);
+    long size = ftell(f);
+    fseek(f, 0, SEEK_SET);
+    char* buffer = malloc(size + 1);
+    fread(buffer, 1, size, f);
+    buffer[size] = 0;
+    fclose(f);
+    return buffer;
+#else
+    return NULL;
+#endif
+}
 
 static void pk_default_print(const char* data) { printf("%s", data); }
 
@@ -147,6 +162,7 @@ void pk_VM__ctor(pk_VM* self) {
     const char** builtin_exceptions = (const char*[]){
         "StackOverflowError",
         "IOError",
+        "OSError",
         "NotImplementedError",
         "TypeError",
         "IndexError",
@@ -174,6 +190,8 @@ void pk_VM__ctor(pk_VM* self) {
 
     // add modules
     pk__add_module_pkpy();
+    pk__add_module_os();
+    pk__add_module_math();
 
     self->main = *py_newmodule("__main__");
 }
@@ -559,7 +577,7 @@ void pk_ManagedHeap__mark(pk_ManagedHeap* self) {
 }
 
 void pk_print_stack(pk_VM* self, Frame* frame, Bytecode byte) {
-    // return;
+    return;
     if(frame == NULL) return;
 
     py_TValue* sp = self->stack.sp;
@@ -594,6 +612,11 @@ void pk_print_stack(pk_VM* self, Frame* frame, Bytecode byte) {
                 pk_sprintf(&buf, "%q", py_tosv(p));
                 break;
             }
+            case tp_module: {
+                py_Ref path = py_getdict(p, __path__);
+                pk_sprintf(&buf, "<module '%v'>", py_tosv(path));
+                break;
+            }
             default: {
                 pk_sprintf(&buf, "(%t)", p->type);
                 break;

+ 202 - 0
src/modules/math.c

@@ -0,0 +1,202 @@
+#include "pocketpy/pocketpy.h"
+
+#include "pocketpy/common/utils.h"
+#include "pocketpy/objects/object.h"
+#include "pocketpy/common/sstream.h"
+#include "pocketpy/interpreter/vm.h"
+
+#include <math.h>
+
+#define ONE_ARG_FUNC(name, func)                                                                   \
+    static bool math_##name(int argc, py_Ref argv) {                                               \
+        PY_CHECK_ARGC(1);                                                                          \
+        double x;                                                                                  \
+        if(!py_castfloat(py_arg(0), &x)) return false;                                             \
+        py_newfloat(py_retval(), func(x));                                                         \
+        return true;                                                                               \
+    }
+
+#define TWO_ARG_FUNC(name, func)                                                                   \
+    static bool math_##name(int argc, py_Ref argv) {                                               \
+        PY_CHECK_ARGC(2);                                                                          \
+        double x, y;                                                                               \
+        if(!py_castfloat(py_arg(0), &x)) return false;                                             \
+        if(!py_castfloat(py_arg(1), &y)) return false;                                             \
+        py_newfloat(py_retval(), func(x, y));                                                      \
+        return true;                                                                               \
+    }
+
+ONE_ARG_FUNC(ceil, ceil)
+ONE_ARG_FUNC(fabs, fabs)
+ONE_ARG_FUNC(floor, floor)
+ONE_ARG_FUNC(trunc, trunc)
+
+static bool math_fsum(int argc, py_Ref argv) {
+    PY_CHECK_ARGC(1);
+    PY_CHECK_ARG_TYPE(0, tp_list);
+    py_Ref list = py_arg(0);
+    double sum = 0;
+    double c = 0;
+    for(int i = 0; i < py_list__len(list); i++) {
+        py_Ref item = py_list__getitem(list, i);
+        double x;
+        if(!py_castfloat(item, &x)) return false;
+        double y = x - c;
+        double t = sum + y;
+        c = (t - sum) - y;
+        sum = t;
+    }
+    py_newfloat(py_retval(), sum);
+    return true;
+}
+
+static bool math_gcd(int argc, py_Ref argv) {
+    PY_CHECK_ARGC(2);
+    PY_CHECK_ARG_TYPE(0, tp_int);
+    PY_CHECK_ARG_TYPE(1, tp_int);
+    py_i64 a = py_toint(py_arg(0));
+    py_i64 b = py_toint(py_arg(1));
+    if(a < 0) a = -a;
+    if(b < 0) b = -b;
+    while(b != 0) {
+        py_i64 t = b;
+        b = a % b;
+        a = t;
+    }
+    py_newint(py_retval(), a);
+    return true;
+}
+
+ONE_ARG_FUNC(isfinite, isfinite)
+ONE_ARG_FUNC(isinf, isinf)
+ONE_ARG_FUNC(isnan, isnan)
+
+static bool math_isclose(int argc, py_Ref argv) {
+    PY_CHECK_ARGC(2);
+    double a, b;
+    if(!py_castfloat(py_arg(0), &a)) return false;
+    if(!py_castfloat(py_arg(1), &b)) return false;
+    py_newbool(py_retval(), fabs(a - b) < 1e-9);
+    return true;
+}
+
+ONE_ARG_FUNC(exp, exp)
+
+static bool math_log(int argc, py_Ref argv) {
+    PY_CHECK_ARG_TYPE(0, tp_float);
+    double x;
+    if(!py_castfloat(py_arg(0), &x)) return false;
+    if(argc == 1) {
+        py_newfloat(py_retval(), log(x));
+    } else if(argc == 2) {
+        double base;
+        if(!py_castfloat(py_arg(1), &base)) return false;
+        py_newfloat(py_retval(), log(x) / log(base));
+    } else {
+        return TypeError("log() takes 1 or 2 arguments");
+    }
+    return true;
+}
+
+ONE_ARG_FUNC(log2, log2)
+ONE_ARG_FUNC(log10, log10)
+
+TWO_ARG_FUNC(pow, pow)
+
+ONE_ARG_FUNC(sqrt, sqrt)
+
+ONE_ARG_FUNC(acos, acos)
+ONE_ARG_FUNC(asin, asin)
+ONE_ARG_FUNC(atan, atan)
+
+ONE_ARG_FUNC(cos, cos)
+ONE_ARG_FUNC(sin, sin)
+ONE_ARG_FUNC(tan, tan)
+
+TWO_ARG_FUNC(atan2, atan2)
+
+static bool math_degrees(int argc, py_Ref argv) {
+    PY_CHECK_ARGC(1);
+    double x;
+    if(!py_castfloat(py_arg(0), &x)) return false;
+    py_newfloat(py_retval(), x * 180 / 3.1415926535897932384);
+    return true;
+}
+
+static bool math_radians(int argc, py_Ref argv) {
+    PY_CHECK_ARGC(1);
+    double x;
+    if(!py_castfloat(py_arg(0), &x)) return false;
+    py_newfloat(py_retval(), x * 3.1415926535897932384 / 180);
+    return true;
+}
+
+static bool math_modf(int argc, py_Ref argv) {
+    PY_CHECK_ARGC(1);
+    double i;
+    double f = modf(py_tofloat(py_arg(0)), &i);
+    py_newtuple(py_retval(), 2);
+    py_Ref _0 = py_tuple__getitem(py_retval(), 0);
+    py_Ref _1 = py_tuple__getitem(py_retval(), 1);
+    py_newfloat(_0, f);
+    py_newfloat(_1, i);
+    return true;
+}
+
+static bool math_factorial(int argc, py_Ref argv) {
+    PY_CHECK_ARGC(1);
+    PY_CHECK_ARG_TYPE(0, tp_int);
+    py_i64 n = py_toint(py_arg(0));
+    if(n < 0) return ValueError("factorial() not defined for negative values");
+    py_i64 r = 1;
+    for(py_i64 i = 2; i <= n; i++)
+        r *= i;
+    py_newint(py_retval(), r);
+    return true;
+}
+
+void pk__add_module_math() {
+    py_Ref mod = py_newmodule("math");
+
+    py_newfloat(py_emplacedict(mod, py_name("pi")), 3.1415926535897932384);
+    py_newfloat(py_emplacedict(mod, py_name("e")), 2.7182818284590452354);
+    py_newfloat(py_emplacedict(mod, py_name("inf")), 1.0 / 0.0);
+    py_newfloat(py_emplacedict(mod, py_name("nan")), 0.0 / 0.0);
+
+    py_bindfunc(mod, "ceil", math_ceil);
+    py_bindfunc(mod, "fabs", math_fabs);
+    py_bindfunc(mod, "floor", math_floor);
+    py_bindfunc(mod, "trunc", math_trunc);
+
+    py_bindfunc(mod, "fsum", math_fsum);
+    py_bindfunc(mod, "gcd", math_gcd);
+
+    py_bindfunc(mod, "isfinite", math_isfinite);
+    py_bindfunc(mod, "isinf", math_isinf);
+    py_bindfunc(mod, "isnan", math_isnan);
+    py_bindfunc(mod, "isclose", math_isclose);
+
+    py_bindfunc(mod, "exp", math_exp);
+    py_bindfunc(mod, "log", math_log);
+    py_bindfunc(mod, "log2", math_log2);
+    py_bindfunc(mod, "log10", math_log10);
+
+    py_bindfunc(mod, "pow", math_pow);
+    py_bindfunc(mod, "sqrt", math_sqrt);
+
+    py_bindfunc(mod, "acos", math_acos);
+    py_bindfunc(mod, "asin", math_asin);
+    py_bindfunc(mod, "atan", math_atan);
+
+    py_bindfunc(mod, "cos", math_cos);
+    py_bindfunc(mod, "sin", math_sin);
+    py_bindfunc(mod, "tan", math_tan);
+
+    py_bindfunc(mod, "atan2", math_atan2);
+
+    py_bindfunc(mod, "degrees", math_degrees);
+    py_bindfunc(mod, "radians", math_radians);
+
+    py_bindfunc(mod, "modf", math_modf);
+    py_bindfunc(mod, "factorial", math_factorial);
+}

+ 49 - 0
src/modules/os.c

@@ -0,0 +1,49 @@
+#include "pocketpy/pocketpy.h"
+
+#include "pocketpy/common/utils.h"
+#include "pocketpy/objects/object.h"
+#include "pocketpy/common/sstream.h"
+#include "pocketpy/interpreter/vm.h"
+
+#if _WIN32
+#include <direct.h>
+
+int platform_chdir(const char* path) { return _chdir(path); }
+
+bool platform_getcwd(char* buf, size_t size) { return _getcwd(buf, size) != NULL; }
+
+#elif __linux__
+#include <unistd.h>
+
+int platform_chdir(const char* path) { return chdir(path); }
+
+bool platform_getcwd(char* buf, size_t size) { return getcwd(buf, size) != NULL; }
+#else
+
+int platform_chdir(const char* path) { return -1; }
+
+bool platform_getcwd(char* buf, size_t size) { return false; }
+
+#endif
+
+static bool os_chdir(int argc, py_Ref argv) {
+    PY_CHECK_ARGC(1);
+    PY_CHECK_ARG_TYPE(0, tp_str);
+    const char* path = py_tostr(py_arg(0));
+    int code = platform_chdir(path);
+    if(code != 0) return OSError("chdir() failed: %d", code);
+    return true;
+}
+
+static bool os_getcwd(int argc, py_Ref argv) {
+    char buf[1024];
+    if(!platform_getcwd(buf, sizeof(buf))) return OSError("getcwd() failed");
+    py_newstr(py_retval(), buf);
+    return true;
+}
+
+void pk__add_module_os() {
+    py_Ref mod = py_newmodule("os");
+    py_bindfunc(mod, "chdir", os_chdir);
+    py_bindfunc(mod, "getcwd", os_getcwd);
+}

+ 0 - 0
src/public/pkpy.c → src/modules/pkpy.c


+ 3 - 5
src/public/cast.c

@@ -19,7 +19,7 @@ bool py_castfloat(py_Ref self, double* out) {
     switch(self->type) {
         case tp_int: *out = (double)self->_i64; return true;
         case tp_float: *out = self->_f64; return true;
-        default: return false;
+        default: return TypeError("expected int or float, got %t", self->type);
     }
 }
 
@@ -46,11 +46,9 @@ bool py_checktype(py_Ref self, py_Type type) {
     return TypeError("expected %t, got %t", type, self->type);
 }
 
-bool py_isinstance(py_Ref obj, py_Type type){
-    return py_issubclass(obj->type, type);
-}
+bool py_isinstance(py_Ref obj, py_Type type) { return py_issubclass(obj->type, type); }
 
-bool py_issubclass(py_Type derived, py_Type base){
+bool py_issubclass(py_Type derived, py_Type base) {
     pk_TypeInfo* types = pk_current_vm->types.data;
     do {
         if(derived == base) return true;

+ 43 - 22
src/public/modules.c

@@ -52,21 +52,53 @@ py_Ref py_newmodule(const char* path) {
     return py_getmodule(path);
 }
 
-bool py_import(const char* path_cstr) {
+int py_import(const char* path_cstr) {
+    // printf("importing %s\n", path_cstr);
+
     pk_VM* vm = pk_current_vm;
     c11_sv path = {path_cstr, strlen(path_cstr)};
     if(path.size == 0) return ValueError("empty module name");
 
     if(path.data[0] == '.') {
         // try relative import
-        py_Ref package = py_getdict(&vm->top_frame->module, __package__);
+        int dot_count = 1;
+        while(dot_count < path.size && path.data[dot_count] == '.')
+            dot_count++;
+
+        c11_sv top_filename = c11_string__sv(vm->top_frame->co->src->filename);
+        int is_init = c11_sv__endswith(top_filename, (c11_sv){"__init__.py", 11});
+
+        py_Ref package = py_getdict(&vm->top_frame->module, __path__);
         c11_sv package_sv = py_tosv(package);
-        if(package_sv.size == 0)
-            return ImportError("relative import %q with no known parent package", path);
-        c11_string* new_path = c11_string__new3("%v.%v", package_sv, path);
-        bool ok = py_import(new_path->data);
+        if(package_sv.size == 0) {
+            return ImportError("attempted relative import with no known parent package");
+        }
+
+        c11_vector /* T=c11_sv */ cpnts = c11_sv__split(package_sv, '.');
+        for(int i = is_init; i < dot_count; i++) {
+            if(cpnts.count == 0)
+                return ImportError("attempted relative import beyond top-level package");
+            c11_vector__pop(&cpnts);
+        }
+
+        if(dot_count < path.size) {
+            c11_sv last_cpnt = c11_sv__slice(path, dot_count);
+            c11_vector__push(c11_sv, &cpnts, last_cpnt);
+        }
+
+        // join cpnts
+        c11_sbuf buf;
+        c11_sbuf__ctor(&buf);
+        for(int i = 0; i < cpnts.count; i++) {
+            if(i > 0) c11_sbuf__write_char(&buf, '.');
+            c11_sbuf__write_sv(&buf, c11__getitem(c11_sv, &cpnts, i));
+        }
+
+        c11_vector__dtor(&cpnts);
+        c11_string* new_path = c11_sbuf__submit(&buf);
+        int res = py_import(new_path->data);
         c11_string__delete(new_path);
-        return ok;
+        return res;
     }
 
     assert(path.data[0] != '.' && path.data[path.size - 1] != '.');
@@ -74,17 +106,10 @@ bool py_import(const char* path_cstr) {
     // check existing module
     py_TmpRef ext_mod = py_getmodule(path.data);
     if(ext_mod) {
-        py_Ref is_pending = py_getdict(ext_mod, __module_is_pending__);
-        if(is_pending) return ImportError("circular import detected");
         py_assign(py_retval(), ext_mod);
         return true;
     }
 
-    // vector<std::string_view> path_cpnts = path.split('.');
-    // // check circular import
-    // if(__import_context.pending.size() > 128) { ImportError("maximum recursion depth exceeded
-    // while importing"); }
-
     // try import
     c11_string* slashed_path = c11_sv__replace(path, '.', PK_PLATFORM_SEP);
     c11_string* filename = c11_string__new3("%s.py", slashed_path->data);
@@ -96,33 +121,29 @@ bool py_import(const char* path_cstr) {
         goto __SUCCESS;
     }
 
-    c11_string__delete(filename);
-    filename = c11_string__new3("%s.py", slashed_path->data);
-    data = vm->import_file(slashed_path->data);
+    data = vm->import_file(filename->data);
     if(data != NULL) goto __SUCCESS;
 
     c11_string__delete(filename);
     filename = c11_string__new3("%s/__init__.py", slashed_path->data);
-    data = vm->import_file(slashed_path->data);
+    data = vm->import_file(filename->data);
     if(data != NULL) goto __SUCCESS;
 
     c11_string__delete(filename);
     c11_string__delete(slashed_path);
-    return ImportError("module %q not found", path);
+    return 0;
 
 __SUCCESS:
     py_push(py_newmodule(path_cstr));
     py_Ref mod = py_peek(-1);
-    py_setdict(mod, __module_is_pending__, py_True);
     bool ok = py_exec((const char*)data, filename->data, EXEC_MODE, mod);
-    py_deldict(mod, __module_is_pending__);
     py_assign(py_retval(), mod);
     py_pop();
 
     c11_string__delete(filename);
     c11_string__delete(slashed_path);
     if(need_free) free((void*)data);
-    return ok;
+    return ok ? 1 : -1;
 }
 
 //////////////////////////

+ 15 - 0
src/public/py_ops.c

@@ -136,6 +136,21 @@ bool py_getattr(py_Ref self, py_Name name) {
         }
     }
 
+    if(self->type == tp_module) {
+        py_Ref path = py_getdict(self, __path__);
+        c11_sbuf buf;
+        c11_sbuf__ctor(&buf);
+        pk_sprintf(&buf, "%v.%n", py_tosv(path), name);
+        c11_string* new_path = c11_sbuf__submit(&buf);
+        int res = py_import(new_path->data);
+        c11_string__delete(new_path);
+        if(res == -1) {
+            return false;
+        } else if(res == 1) {
+            return true;
+        }
+    }
+
     return AttributeError(self, name);
 }
 

+ 4 - 12
src/public/py_str.c

@@ -247,9 +247,7 @@ static bool _py_str__startswith(int argc, py_Ref argv) {
     c11_string* self = py_touserdata(&argv[0]);
     PY_CHECK_ARG_TYPE(1, tp_str);
     c11_string* other = py_touserdata(&argv[1]);
-    c11_sv _0 = c11_sv__slice2(c11_string__sv(self), 0, other->size);
-    c11_sv _1 = c11_string__sv(other);
-    py_newbool(py_retval(), c11__sveq(_0, _1));
+    py_newbool(py_retval(), c11_sv__startswith(c11_string__sv(self), c11_string__sv(other)));
     return true;
 }
 
@@ -258,9 +256,7 @@ static bool _py_str__endswith(int argc, py_Ref argv) {
     c11_string* self = py_touserdata(&argv[0]);
     PY_CHECK_ARG_TYPE(1, tp_str);
     c11_string* other = py_touserdata(&argv[1]);
-    c11_sv _0 = c11_sv__slice2(c11_string__sv(self), self->size - other->size, self->size);
-    c11_sv _1 = c11_string__sv(other);
-    py_newbool(py_retval(), c11__sveq(_0, _1));
+    py_newbool(py_retval(), c11_sv__endswith(c11_string__sv(self), c11_string__sv(other)));
     return true;
 }
 
@@ -538,10 +534,6 @@ bool py_str(py_Ref val) {
     return py_call(tmp, 1, val);
 }
 
-bool py_repr(py_Ref val) {
-    return pk_callmagic(__repr__, 1, val);
-}
+bool py_repr(py_Ref val) { return pk_callmagic(__repr__, 1, val); }
 
-bool py_len(py_Ref val){
-    return pk_callmagic(__len__, 1, val);
-}
+bool py_len(py_Ref val) { return pk_callmagic(__len__, 1, val); }

+ 5 - 0
src/public/stack_ops.c

@@ -29,6 +29,11 @@ void py_setdict(py_Ref self, py_Name name, py_Ref val) {
     }
 }
 
+py_TmpRef py_emplacedict(py_Ref self, py_Name name){
+    py_setdict(self, name, py_NIL);
+    return py_getdict(self, name);
+}
+
 bool py_deldict(py_Ref self, py_Name name) {
     assert(self && self->is_ptr);
     if(!py_ismagicname(name) || self->type != tp_type) {

+ 1 - 0
tests/30_import.py

@@ -4,6 +4,7 @@ except ImportError:
     exit(0)
 
 os.chdir('tests')
+assert os.getcwd().endswith('tests')
 
 import test1
 

+ 0 - 0
tests/40_class_ex.py → tests/41_class_ex.py


+ 0 - 0
tests/42_closure_ex.py → tests/43_closure_ex.py


+ 0 - 0
tests/43_eval.py → tests/44_eval.py


+ 0 - 0
tests/34_context.py → tests/67_context.py