blueloveTH hace 1 año
padre
commit
1a4b88829c

+ 6 - 0
include/pocketpy/interpreter/vm.h

@@ -66,6 +66,9 @@ void pk_VM__pop_frame(pk_VM* self);
 bool pk__parse_int_slice(const py_Ref slice, int length, int* start, int* stop, int* step);
 bool pk__normalize_index(int* index, int length);
 
+void pk_list__mark(void* ud, void (*marker)(py_TValue*));
+void pk_dict__mark(void* ud, void (*marker)(py_TValue*));
+
 typedef enum pk_FrameResult {
     RES_RETURN,
     RES_CALL,
@@ -88,11 +91,13 @@ const char* pk_opname(Opcode op);
 
 py_TValue* pk_arrayview(py_Ref self, int* length);
 int pk_arrayeq(py_TValue* lhs, int lhs_length, py_TValue* rhs, int rhs_length);
+bool pk_arrayiter(py_Ref val);
 
 /// Assumes [a, b] are on the stack, performs a binary op.
 /// The result is stored in `self->last_retval`.
 /// The stack remains unchanged.
 bool pk_stack_binaryop(pk_VM* self, py_Name op, py_Name rop);
+
 void pk_print_stack(pk_VM* self, Frame* frame, Bytecode byte);
 
 // type registration
@@ -102,6 +107,7 @@ py_Type pk_str__register();
 py_Type pk_str_iterator__register();
 py_Type pk_bytes__register();
 py_Type pk_dict__register();
+py_Type pk_dict_items__register();
 py_Type pk_list__register();
 py_Type pk_tuple__register();
 py_Type pk_array_iterator__register();

+ 0 - 113
include/pocketpy/objects/dict.h

@@ -1,113 +0,0 @@
-#pragma once
-
-#include <stdbool.h>
-#include "pocketpy/objects/base.h"
-#include "pocketpy/common/vector.h"
-
-#ifdef __cplusplus
-extern "C" {
-#endif
-
-/** @brief `pkpy_Dict` is the Dict type in Python */
-typedef struct {
-    int count;             /** number of elements in the dictionary */
-    c11_vector _entries;   /** contains `pkpy_DictEntry` (hidden type) */
-    int _htcap;            /** capacity of the hashtable, always a power of 2 */
-    void* _hashtable;      /** contains indecies, can be `u8`, `u16` or `u32` according to size*/
-} pkpy_Dict;
-
-/** @brief `pkpy_DictIter` is used to iterate over a `pkpy_Dict` */
-typedef struct {
-    const pkpy_Dict* _dict;
-    int _index;
-} pkpy_DictIter;
-
-/**
- * @brief `pkpy_Dict` constructor
- * @param self `pkpy_Dict` instance
- */
-void pkpy_Dict__ctor(pkpy_Dict* self);
-
-/**
- * @brief `pkpy_Dict` destructor
- * @param self `pkpy_Dict` instance
- */
-void pkpy_Dict__dtor(pkpy_Dict* self);
-
-/**
- * @brief Copy a `pkpy_Dict`
- * @param self `pkpy_Dict` instance
- * @return a new `pkpy_Dict` instance, must be destructed by the caller
- */
-pkpy_Dict pkpy_Dict__copy(const pkpy_Dict* self);
-
-/**
- * @brief Set a key-value pair into the `pkpy_Dict`
- * @param self `pkpy_Dict` instance
- * @param vm __eq__ and __hash__ context
- * @param key key to set
- * @param val value to set
- * @return `true` if the key is newly added, `false` if the key already exists
- */
-bool pkpy_Dict__set(pkpy_Dict* self, py_TValue key, py_TValue val);
-
-/**
- * @brief Check if a key exists in the `pkpy_Dict`
- * @param self `pkpy_Dict` instance
- * @param vm __eq__ and __hash__ context
- * @param key key to check
- * @return `true` if the key exists, `false` otherwise
- */
-bool pkpy_Dict__contains(const pkpy_Dict* self, py_TValue key);
-
-/**
- * @brief Remove a key from the `pkpy_Dict`
- * @param self `pkpy_Dict` instance
- * @param vm __eq__ and __hash__ context
- * @param key key to remove
- * @return `true` if the key was found and removed, `false` if the key doesn't exist
- */
-bool pkpy_Dict__del(pkpy_Dict* self, py_TValue key);
-
-/**
- * @brief Try to get a value from the `pkpy_Dict`
- * @param self `pkpy_Dict` instance
- * @param vm __eq__ and __hash__ context
- * @param key key to get
- * @return the value associated with the key, `NULL` if the key doesn't exist
- */
-const py_TValue* pkpy_Dict__try_get(const pkpy_Dict* self, py_TValue key);
-
-/**
- * @brief Update the `pkpy_Dict` with another one
- * @param self `pkpy_Dict` instance
- * @param vm __eq__ and __hash__ context
- * @param other `pkpy_Dict` instance to update with
- */
-void pkpy_Dict__update(pkpy_Dict* self, const pkpy_Dict* other);
-
-/**
- * @brief Clear the `pkpy_Dict`
- * @param self `pkpy_Dict` instance
- */
-void pkpy_Dict__clear(pkpy_Dict* self);
-
-/**
- * @brief Iterate over the `pkpy_Dict`
- * @param self `pkpy_Dict` instance
- * @return an iterator over the `pkpy_Dict`
- */
-pkpy_DictIter pkpy_Dict__iter(const pkpy_Dict* self);
-
-/**
- * @brief Iterate over the `pkpy_Dict`
- * @param self `pkpy_Dict` instance
- * @param key key will be filled with the current key, can be `NULL` if not needed
- * @param value value will be filled with the current value, can be `NULL` if not needed
- * @return `true` if the iteration is still valid, `false` otherwise
- */
-bool pkpy_DictIter__next(pkpy_DictIter* self, py_TValue* key, py_TValue* value);
-
-#ifdef __cplusplus
-}
-#endif

+ 19 - 6
include/pocketpy/pocketpy.h

@@ -121,11 +121,18 @@ void* py_touserdata(const py_Ref);
 #define py_isfloat(self) py_istype(self, tp_float)
 #define py_isbool(self) py_istype(self, tp_bool)
 #define py_isstr(self) py_istype(self, tp_str)
+#define py_islist(self) py_istype(self, tp_list)
+#define py_istuple(self) py_istype(self, tp_tuple)
+#define py_isdict(self) py_istype(self, tp_dict)
 
 bool py_istype(const py_Ref, py_Type);
 bool py_isinstance(const py_Ref obj, py_Type type);
 bool py_issubclass(py_Type derived, py_Type base);
 
+extern py_GlobalRef py_True;
+extern py_GlobalRef py_False;
+extern py_GlobalRef py_None;
+
 /************* References *************/
 #define PY_CHECK_ARGC(n)                                                                           \
     if(argc != n) return TypeError("expected %d arguments, got %d", n, argc)
@@ -195,7 +202,7 @@ bool py_delattr(py_Ref self, py_Name name);
 /// Gets the unbound method of the object.
 bool py_getunboundmethod(py_Ref self, py_Name name, py_Ref out, py_Ref out_self);
 
-bool py_getitem(const py_Ref self, const py_Ref key, py_Ref out);
+bool py_getitem(const py_Ref self, const py_Ref key);
 bool py_setitem(py_Ref self, const py_Ref key, const py_Ref val);
 bool py_delitem(py_Ref self, const py_Ref key);
 
@@ -236,8 +243,6 @@ void py_pop();
 void py_shrink(int n);
 /// Get a temporary variable from the stack and returns the reference to it.
 py_StackRef py_pushtmp();
-/// Free n temporary variable.
-#define py_poptmp(n) py_shrink(n)
 
 #define py_gettop() py_peek(-1)
 #define py_getsecond() py_peek(-2)
@@ -262,9 +267,9 @@ void py_formatexc(char* out);
 /// Check if an error is set.
 bool py_checkexc();
 
-#define KeyError(q) py_exception("KeyError", "%q", (q))
 #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__)
 #define ValueError(...) py_exception("ValueError", __VA_ARGS__)
 #define IndexError(...) py_exception("IndexError", __VA_ARGS__)
 #define NotImplementedError() py_exception("NotImplementedError", "")
@@ -274,6 +279,7 @@ bool py_checkexc();
     py_exception("UnboundLocalError", "local variable '%n' referenced before assignment", (n))
 
 bool StopIteration();
+bool KeyError(py_Ref key);
 
 /************* Operators *************/
 /// Equivalent to `bool(val)`.
@@ -338,8 +344,8 @@ void py_tuple__setitem(py_Ref self, int i, const py_Ref val);
 int py_tuple__len(const py_Ref self);
 
 // unchecked functions, if self is not a list, the behavior is undefined
-py_ObjectRef py_list__data(const py_Ref self);
-py_ObjectRef py_list__getitem(const py_Ref self, int i);
+py_TmpRef py_list__data(const py_Ref self);
+py_TmpRef py_list__getitem(const py_Ref self, int i);
 void py_list__setitem(py_Ref self, int i, const py_Ref val);
 void py_list__delitem(py_Ref self, int i);
 int py_list__len(const py_Ref self);
@@ -348,6 +354,12 @@ void py_list__clear(py_Ref self);
 void py_list__insert(py_Ref self, int i, const py_Ref val);
 void py_list__reverse(py_Ref self);
 
+// unchecked functions, if self is not a dict, the behavior is undefined
+py_TmpRef py_dict__getitem(const py_Ref self, const py_Ref key);
+void py_dict__setitem(py_Ref self, const py_Ref key, const py_Ref val);
+bool py_dict__contains(const py_Ref self, const py_Ref key);
+int py_dict__len(const py_Ref self);
+
 /// Search the magic method from the given type to the base type.
 /// Return the reference or NULL if not found.
 py_GlobalRef py_tpfindmagic(py_Type, py_Name name);
@@ -419,6 +431,7 @@ enum py_PredefinedTypes {
     tp_bytes,
     tp_mappingproxy,
     tp_dict,
+    tp_dict_items,    // 1 slot
     tp_property,      // 2 slots (getter + setter)
     tp_star_wrapper,  // 1 slot + int level
     tp_staticmethod,  // 1 slot

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

@@ -58,6 +58,7 @@ MAGIC_METHOD(__all__)
 MAGIC_METHOD(__package__)
 MAGIC_METHOD(__path__)
 MAGIC_METHOD(__class__)
+MAGIC_METHOD(__abs__)
 MAGIC_METHOD(__missing__)
 
 #endif

+ 37 - 8
src/compiler/compiler.c

@@ -310,8 +310,7 @@ void LiteralExpr__emit_(Expr* self_, Ctx* ctx) {
 }
 
 LiteralExpr* LiteralExpr__new(int line, const TokenValue* value) {
-    const static ExprVt Vt = {.emit_ = LiteralExpr__emit_,
-                              .is_literal = true};
+    const static ExprVt Vt = {.emit_ = LiteralExpr__emit_, .is_literal = true};
     static_assert_expr_size(LiteralExpr);
     LiteralExpr* self = PoolExpr_alloc();
     self->vt = &Vt;
@@ -392,6 +391,35 @@ SliceExpr* SliceExpr__new(int line) {
     return self;
 }
 
+typedef struct DictItemExpr {
+    EXPR_COMMON_HEADER
+    Expr* key;
+    Expr* value;
+} DictItemExpr;
+
+static void DictItemExpr__dtor(Expr* self_) {
+    DictItemExpr* self = (DictItemExpr*)self_;
+    vtdelete(self->key);
+    vtdelete(self->value);
+}
+
+static void DictItemExpr__emit_(Expr* self_, Ctx* ctx) {
+    DictItemExpr* self = (DictItemExpr*)self_;
+    vtemit_(self->key, ctx);
+    vtemit_(self->value, ctx);
+}
+
+static DictItemExpr* DictItemExpr__new(int line) {
+    const static ExprVt Vt = {.dtor = DictItemExpr__dtor, .emit_ = DictItemExpr__emit_};
+    static_assert_expr_size(DictItemExpr);
+    DictItemExpr* self = PoolExpr_alloc();
+    self->vt = &Vt;
+    self->line = line;
+    self->key = NULL;
+    self->value = NULL;
+    return self;
+}
+
 // ListExpr, DictExpr, SetExpr, TupleExpr
 typedef struct SequenceExpr {
     EXPR_COMMON_HEADER
@@ -477,14 +505,12 @@ static SequenceExpr* SequenceExpr__new(int line, const ExprVt* vt, int count, Op
 }
 
 SequenceExpr* ListExpr__new(int line, int count) {
-    const static ExprVt ListExprVt = {.dtor = SequenceExpr__dtor,
-                                      .emit_ = SequenceExpr__emit_};
+    const static ExprVt ListExprVt = {.dtor = SequenceExpr__dtor, .emit_ = SequenceExpr__emit_};
     return SequenceExpr__new(line, &ListExprVt, count, OP_BUILD_LIST);
 }
 
 SequenceExpr* DictExpr__new(int line, int count) {
-    const static ExprVt DictExprVt = {.dtor = SequenceExpr__dtor,
-                                      .emit_ = SequenceExpr__emit_};
+    const static ExprVt DictExprVt = {.dtor = SequenceExpr__dtor, .emit_ = SequenceExpr__emit_};
     return SequenceExpr__new(line, &DictExprVt, count, OP_BUILD_DICT);
 }
 
@@ -1865,7 +1891,11 @@ static Error* exprMap(Compiler* self) {
         if(curr()->type == TK_COLON) { parsing_dict = true; }
         if(parsing_dict) {
             consume(TK_COLON);
-            check(EXPR(self));  // [key, value]
+            check(EXPR(self));  // [key, value] -> [item]
+            DictItemExpr* item = DictItemExpr__new(prev()->line);
+            item->value = Ctx__s_popx(ctx());
+            item->key = Ctx__s_popx(ctx());
+            Ctx__s_push(ctx(), (Expr*)item);
         }
         count += 1;  // key-value pair count
         match_newlines();
@@ -1884,7 +1914,6 @@ static Error* exprMap(Compiler* self) {
 
     SequenceExpr* se;
     if(count == 0 || parsing_dict) {
-        count *= 2;  // key + value
         se = DictExpr__new(line, count);
     } else {
         se = SetExpr__new(line, count);

+ 22 - 13
src/interpreter/ceval.c

@@ -317,15 +317,14 @@ pk_FrameResult pk_VM__run_top_frame(pk_VM* self) {
             }
             case OP_STORE_SUBSCR: {
                 // [val, a, b] -> a[b] = val
-                PUSH(THIRD());  // [val, a, b, val]
                 py_Ref magic = py_tpfindmagic(SECOND()->type, __setitem__);
                 if(magic) {
+                    PUSH(THIRD());  // [val, a, b, val]
                     if(magic->type == tp_nativefunc) {
-                        py_TValue* next_sp = THIRD();
+                        py_TValue* next_sp = FOURTH();
                         bool ok = magic->_cfunc(3, THIRD());
                         if(!ok) goto __ERROR;
                         SP() = next_sp;
-                        *TOP() = self->last_retval;
                     } else {
                         *FOURTH() = *magic;  // [__selitem__, a, b, val]
                         if(!py_vectorcall(2, 0)) goto __ERROR;
@@ -423,10 +422,10 @@ pk_FrameResult pk_VM__run_top_frame(pk_VM* self) {
                 py_Ref f = py_getdict(&self->builtins, py_name("complex"));
                 assert(f != NULL);
                 py_TValue tmp = *TOP();
-                *TOP() = *f;              // [complex]
-                py_newnil(SP()++);        // [complex, NULL]
-                py_newint(SP()++, 0);     // [complex, NULL, 0]
-                *SP()++ = tmp;            // [complex, NULL, 0, x]
+                *TOP() = *f;           // [complex]
+                py_newnil(SP()++);     // [complex, NULL]
+                py_newint(SP()++, 0);  // [complex, NULL, 0]
+                *SP()++ = tmp;         // [complex, NULL, 0, x]
                 if(!py_vectorcall(2, 0)) goto __ERROR;
                 DISPATCH();
             }
@@ -460,11 +459,12 @@ pk_FrameResult pk_VM__run_top_frame(pk_VM* self) {
                 DISPATCH();
             }
             case OP_BUILD_DICT: {
-                py_TValue* begin = SP() - byte.arg;
+                py_TValue* begin = SP() - byte.arg * 2;
                 py_Ref tmp = py_pushtmp();
                 py_newdict(tmp);
-                for(int i = 0; i < byte.arg; i += 2) {
-                    if(!py_setitem(tmp, begin + i, begin + i + 1)) goto __ERROR;
+                for(int i = 0; i < byte.arg * 2; i += 2) {
+                    py_dict__setitem(tmp, begin + i, begin + i + 1);
+                    if(py_checkexc()) goto __ERROR;
                 }
                 SP() = begin;
                 PUSH(tmp);
@@ -477,7 +477,7 @@ pk_FrameResult pk_VM__run_top_frame(pk_VM* self) {
                 py_push(typeobject_set);
                 py_pushnil();
                 if(!py_vectorcall(0, 0)) goto __ERROR;
-                py_push(py_retval());   // empty set
+                py_push(py_retval());  // empty set
                 py_Name id_add = py_name("add");
                 for(int i = 0; i < byte.arg; i++) {
                     if(!py_callmethod(TOP(), id_add, 1, begin + i)) goto __ERROR;
@@ -687,7 +687,7 @@ pk_FrameResult pk_VM__run_top_frame(pk_VM* self) {
                 for(int i = size - 1; i >= 0; i--) {
                     PUSH(buf + i);
                 }
-                
+
                 vectorcall_opcall(new_argc, new_kwargc);
                 DISPATCH();
             }
@@ -718,6 +718,10 @@ pk_FrameResult pk_VM__run_top_frame(pk_VM* self) {
                 DISPATCH();
             }
             case OP_DICT_ADD: {
+                // [dict, iter, key, value]
+                py_dict__setitem(FOURTH(), SECOND(), TOP());
+                if(py_checkexc()) goto __ERROR;
+                STACK_SHRINK(2);
                 DISPATCH();
             }
             case OP_SET_ADD: {
@@ -839,7 +843,12 @@ bool pk_stack_binaryop(pk_VM* self, py_Name op, py_Name rop) {
             if(self->last_retval.type != tp_NotImplementedType) return true;
         }
     }
-    // eq/ne op never fails due to object.__eq__
+    // eq/ne op never fails
+    if(op == __eq__ || op == __ne__) {
+        bool res = py_isidentical(SECOND(), TOP());
+        py_newbool(py_retval(), res);
+        return true;
+    }
     return py_exception("TypeError", "unsupported operand type(s) for '%n'", op);
 }
 

+ 28 - 24
src/interpreter/vm.c

@@ -117,6 +117,8 @@ void pk_VM__ctor(pk_VM* self) {
     validate(tp_mappingproxy, pk_newtype("mappingproxy", tp_object, NULL, NULL, false, true));
 
     validate(tp_dict, pk_dict__register());
+    validate(tp_dict_items, pk_dict_items__register());
+
     validate(tp_property, pk_newtype("property", tp_object, NULL, NULL, false, true));
     validate(tp_star_wrapper, pk_newtype("star_wrapper", tp_object, NULL, NULL, false, true));
 
@@ -330,9 +332,12 @@ static bool
                                  co->name->data);
             } else {
                 // add to **kwargs
-                // Dict& dict = _CAST(Dict&, vkwargs);
-                // dict.set(this, VAR(key.sv()), kwargs[j + 1]);
-                assert(false);
+                py_Ref tmp = py_pushtmp();
+                c11_sv key_sv = py_name2sv(key);
+                py_newstrn(tmp, key_sv.data, key_sv.size);
+                py_dict__setitem(&buffer[decl->starred_kwarg], tmp, &p1[2 * j + 1]);
+                if(py_checkexc()) return false;
+                py_pop();
             }
         }
     }
@@ -467,36 +472,31 @@ pk_FrameResult pk_VM__vectorcall(pk_VM* self, uint16_t argc, uint16_t kwargc, bo
         memcpy(self->stack.sp, argv, span * sizeof(py_TValue));
         self->stack.sp += span;
         // [new_f, cls, args..., kwargs...]
-        pk_FrameResult res = pk_VM__vectorcall(self, argc, kwargc, false);
-        if(res == RES_ERROR) return RES_ERROR;
-        assert(res == RES_RETURN);
+        if(pk_VM__vectorcall(self, argc, kwargc, false) == RES_ERROR) return RES_ERROR;
         // by recursively using vectorcall, args and kwargs are consumed
-        // [cls, NULL, args..., kwargs...]
 
         // try __init__
-        // NOTE: previous we use `get_unbound_method` but here we just use `tpfindmagic`
+        // NOTE: previously we use `get_unbound_method` but here we just use `tpfindmagic`
+        // >> [cls, NULL, args..., kwargs...]
+        // >> py_retval() is the new instance
         py_Ref init_f = py_tpfindmagic(py_totype(p0), __init__);
         if(init_f) {
             // do an inplace patch
             *p0 = *init_f;              // __init__
             p0[1] = self->last_retval;  // self
             // [__init__, self, args..., kwargs...]
-            pk_FrameResult res = pk_VM__vectorcall(self, argc, kwargc, false);
-            if(res == RES_ERROR) return RES_ERROR;
-            assert(res == RES_RETURN);
-        } else {
-            // manually reset the stack
-            self->stack.sp = p0;
+            if(pk_VM__vectorcall(self, argc, kwargc, false) == RES_ERROR) return RES_ERROR;
+            *py_retval() = p0[1];  // restore the new instance
         }
+        // reset the stack
+        self->stack.sp = p0;
         return RES_RETURN;
     }
 
     // handle `__call__` overload
     if(py_getunboundmethod(p0, __call__, p0, p0 + 1)) {
         // [__call__, self, args..., kwargs...]
-        pk_FrameResult res = pk_VM__vectorcall(self, argc, kwargc, false);
-        if(res == RES_ERROR) return RES_ERROR;
-        assert(res == RES_RETURN);
+        return pk_VM__vectorcall(self, argc, kwargc, opcall);
     }
 
     TypeError("'%t' object is not callable", p0->type);
@@ -525,13 +525,6 @@ static void mark_object(PyObject* obj) {
     if(obj->gc_marked) return;
     obj->gc_marked = true;
 
-    // list is a special case
-    if(obj->type == tp_list) {
-        c11_vector* vec = PyObject__userdata(obj);
-        c11__foreach(py_TValue, vec, p) mark_value(p);
-        return;
-    }
-
     if(obj->slots > 0) {
         py_TValue* p = PyObject__slots(obj);
         for(int i = 0; i < obj->slots; i++)
@@ -547,6 +540,16 @@ static void mark_object(PyObject* obj) {
         }
         return;
     }
+
+    if(obj->type == tp_list) {
+        pk_list__mark(PyObject__userdata(obj), mark_value);
+        return;
+    }
+
+    if(obj->type == tp_dict) {
+        pk_dict__mark(PyObject__userdata(obj), mark_value);
+        return;
+    }
 }
 
 void pk_ManagedHeap__mark(pk_ManagedHeap* self) {
@@ -573,6 +576,7 @@ void pk_ManagedHeap__mark(pk_ManagedHeap* self) {
 
 void pk_print_stack(pk_VM* self, Frame* frame, Bytecode byte) {
     return;
+
     py_TValue* sp = self->stack.sp;
     c11_sbuf buf;
     c11_sbuf__ctor(&buf);

+ 0 - 276
src/objects/dict.c

@@ -1,276 +0,0 @@
-#include "pocketpy/objects/dict.h"
-#include "pocketpy/common/utils.h"
-#include <stdint.h>
-#include <stdlib.h>
-#include <assert.h>
-#include <string.h>
-
-#define DICT_MAX_LOAD 0.75
-#define DICT_HASH_NEXT(h) ((h) * 5 + 1)
-#define DICT_HASH_TRANS(h) ((int)((h) & 0xffffffff)) // used for tansform value from __hash__
-#define PK_DICT_COMPACT_MODE 1
-#define pkpy_Var__is_null(self) ((self)->type == 0)
-#define pkpy_Var__set_null(self) do { (self)->type = 0; } while(0)
-
-struct pkpy_DictEntry {
-    py_TValue key;
-    py_TValue val;
-};
-
-inline extern int pkpy_Dict__idx_size(const pkpy_Dict* self) {
-#if PK_DICT_COMPACT_MODE
-    if(self->_htcap < 255) return 1;
-    if(self->_htcap < 65535) return 2;
-#endif
-    return 4;
-}
-
-inline extern int pkpy_Dict__idx_null(const pkpy_Dict* self) {
-#if PK_DICT_COMPACT_MODE
-    if(self->_htcap < 255) return 255;
-    if(self->_htcap < 65535) return 65535;
-#endif
-    return -1;
-}
-
-inline extern int pkpy_Dict__ht_byte_size(const pkpy_Dict* self) { return self->_htcap * pkpy_Dict__idx_size(self); }
-
-void pkpy_Dict__ctor(pkpy_Dict* self) {
-    self->count = 0;
-    c11_vector__ctor(&self->_entries, sizeof(struct pkpy_DictEntry));
-    self->_htcap = 16;
-    self->_hashtable = malloc(pkpy_Dict__ht_byte_size(self));
-    memset(self->_hashtable, 0xff, pkpy_Dict__ht_byte_size(self));
-}
-
-void pkpy_Dict__dtor(pkpy_Dict* self) {
-    c11_vector__dtor(&self->_entries);
-    free(self->_hashtable);
-}
-
-pkpy_Dict pkpy_Dict__copy(const pkpy_Dict* self) {
-    int ht_size = pkpy_Dict__ht_byte_size(self);
-    void* ht_clone = malloc(ht_size);
-    memcpy(ht_clone, self->_hashtable, ht_size);
-    return (pkpy_Dict){.count = self->count,
-                       ._entries = c11_vector__copy(&self->_entries),
-                       ._htcap = self->_htcap,
-                       ._hashtable = ht_clone};
-}
-
-static int pkpy_Dict__htget(const pkpy_Dict* self, int h) {
-#if PK_DICT_COMPACT_MODE
-    const int loc = pkpy_Dict__idx_size(self) * h;
-    const int *p = (const int*)(((const char*)self->_hashtable) + (loc & (~3)));
-    return (*p >> ((loc & 3) * 8)) & pkpy_Dict__idx_null(self);
-#else
-    return ((const int*)self->_hashtable)[h];
-#endif
-}
-
-static void pkpy_Dict__htset(pkpy_Dict* self, int h, int v) {
-#if PK_DICT_COMPACT_MODE
-    const int loc = pkpy_Dict__idx_size(self) * h;
-    int *p = (int*)(((char*)self->_hashtable) + (loc & (~3)));
-    const int shift = (loc & 3) * 8;
-    *p = (v << shift) | (*p & ~(pkpy_Dict__idx_null(self) << shift));
-#else
-    ((int*)self->_hashtable)[h] = v;
-#endif
-}
-
-static int pkpy_Dict__probe0(const pkpy_Dict* self, py_TValue key, int hash) {
-    const int null = pkpy_Dict__idx_null(self);
-    const int mask = self->_htcap - 1;
-    for(int h = hash & mask;; h = DICT_HASH_NEXT(h) & mask) {
-        int idx = pkpy_Dict__htget(self, h);
-        if(idx == null) return h;
-
-        struct pkpy_DictEntry* entry = &c11__getitem(struct pkpy_DictEntry, &self->_entries, idx);
-        if(pkpy_Var__is_null(&entry->key)) return h;
-    }
-    c11__unreachedable();
-}
-
-static int pkpy_Dict__probe1(const pkpy_Dict* self, py_TValue key, int hash) {
-    const int null = pkpy_Dict__idx_null(self);
-    const int mask = self->_htcap - 1;
-    for(int h = hash & mask;; h = DICT_HASH_NEXT(h) & mask) {
-        int idx = pkpy_Dict__htget(self, h);
-        if(idx == null) return h;
-
-        struct pkpy_DictEntry* entry = &c11__getitem(struct pkpy_DictEntry, &self->_entries, idx);
-        if(pkpy_Var__is_null(&entry->key)) continue;
-        if(py_eq(&entry->key, &key)) return h;
-    }
-    c11__unreachedable();
-}
-
-static void pkpy_Dict__extendht(pkpy_Dict* self) {
-    free(self->_hashtable);
-    self->_htcap *= 2;
-    self->_hashtable = malloc(pkpy_Dict__ht_byte_size(self));
-    memset(self->_hashtable, 0xff, pkpy_Dict__ht_byte_size(self));
-
-    for(int i = 0; i < self->_entries.count; i++) {
-        struct pkpy_DictEntry* entry = &c11__getitem(struct pkpy_DictEntry, &self->_entries, i);
-        if(pkpy_Var__is_null(&entry->key)) continue;
-
-        int64_t out;
-        int err = py_hash(&entry->key, &out);
-        int rhash = DICT_HASH_TRANS(out);
-        int h = pkpy_Dict__probe0(self, entry->key, rhash);
-        pkpy_Dict__htset(self, h, i);
-    }
-}
-
-bool pkpy_Dict__set(pkpy_Dict* self, py_TValue key, py_TValue val) {
-    int64_t out;
-    int err = py_hash(&key, &out);
-    int hash = DICT_HASH_TRANS(out);
-    int h = pkpy_Dict__probe1(self, key, hash);
-
-    int idx = pkpy_Dict__htget(self, h);
-    if(idx == pkpy_Dict__idx_null(self)) {
-        idx = self->_entries.count;
-        c11_vector__push(struct pkpy_DictEntry,
-                         &self->_entries,
-                         ((struct pkpy_DictEntry){
-                             .key = key,
-                             .val = val,
-                         }));
-        h = pkpy_Dict__probe0(self, key, hash);
-        pkpy_Dict__htset(self, h, idx);
-        self->count += 1;
-        if(self->count >= self->_htcap * DICT_MAX_LOAD) pkpy_Dict__extendht(self);
-        return true;
-    }
-
-    struct pkpy_DictEntry* entry = &c11__getitem(struct pkpy_DictEntry, &self->_entries, idx);
-
-    if(py_eq(&entry->key, &key)) {
-        entry->val = val;
-    } else {
-        self->count += 1;
-        h = pkpy_Dict__probe0(self, key, hash);
-        idx = pkpy_Dict__htget(self, h);
-        struct pkpy_DictEntry* entry = &c11__getitem(struct pkpy_DictEntry, &self->_entries, idx);
-        entry->key = key;
-        entry->val = val;
-    }
-    return false;
-}
-
-bool pkpy_Dict__contains(const pkpy_Dict* self, py_TValue key) {
-    int64_t out;
-    int err = py_hash(&key, &out);
-    int hash = DICT_HASH_TRANS(out);
-    int h = pkpy_Dict__probe1(self, key, hash);
-
-    int idx = pkpy_Dict__htget(self, h);
-    if(idx == pkpy_Dict__idx_null(self)) return false;
-    return true;
-}
-
-static bool pkpy_Dict__refactor(pkpy_Dict* self) {
-    int deleted_slots = self->_entries.count - self->count;
-    if(deleted_slots <= 8 || deleted_slots < self->_entries.count * (1 - DICT_MAX_LOAD)) return false;
-
-    // shrink
-    // free(self->_hashtable);
-    // while(self->_htcap * DICT_MAX_LOAD / 2 > self->count && self->_htcap >= 32)
-    //     self->_htcap /= 2;
-    // self->_hashtable = malloc(pkpy_Dict__ht_byte_size(self));
-    memset(self->_hashtable, 0xff, pkpy_Dict__ht_byte_size(self));
-
-    int new_cnt = 0;
-    for (int i = 0; i < self->_entries.count; ++i) {
-        struct pkpy_DictEntry* entry = &c11__getitem(struct pkpy_DictEntry, &self->_entries, i);
-        if(pkpy_Var__is_null(&entry->key)) continue;
-        if (i > new_cnt) c11__setitem(struct pkpy_DictEntry, &self->_entries, new_cnt, *entry);
-        new_cnt += 1;
-    }
-
-    self->_entries.count = new_cnt;
-    for(int i = 0; i < self->_entries.count; i++) {
-        struct pkpy_DictEntry* entry = &c11__getitem(struct pkpy_DictEntry, &self->_entries, i);
-        if(pkpy_Var__is_null(&entry->key)) continue;
-
-        int64_t out;
-        py_hash(&entry->key, &out);
-        int rhash = DICT_HASH_TRANS(out);
-        int h = pkpy_Dict__probe0(self, entry->key, rhash);
-        pkpy_Dict__htset(self, h, i);
-    }
-    return true;
-}
-
-bool pkpy_Dict__del(pkpy_Dict* self, py_TValue key) {
-    int64_t out;
-    int err = py_hash(&key, &out);
-    int hash = DICT_HASH_TRANS(out);
-    int h = pkpy_Dict__probe1(self, key, hash);
-    int idx = pkpy_Dict__htget(self, h), null = pkpy_Dict__idx_null(self);
-    if(idx == null) return false;
-    
-    struct pkpy_DictEntry* entry = &c11__getitem(struct pkpy_DictEntry, &self->_entries, idx);
-    pkpy_Var__set_null(&entry->key);
-    self->count -= 1;
-    pkpy_Dict__refactor(self);
-    return true;
-}
-
-const py_TValue *pkpy_Dict__try_get(const pkpy_Dict* self, py_TValue key) {
-    int64_t out;
-    int err = py_hash(&key, &out);
-    int hash = DICT_HASH_TRANS(out);
-    int h = pkpy_Dict__probe1(self, key, hash);
-    
-    int idx = pkpy_Dict__htget(self, h);
-    if(idx == pkpy_Dict__idx_null(self)) return NULL;
-    
-    struct pkpy_DictEntry* entry = &c11__getitem(struct pkpy_DictEntry, &self->_entries, idx);
-    return &entry->val;
-}
-
-void pkpy_Dict__update(pkpy_Dict *self, const pkpy_Dict *other) {
-    for(int i = 0; i < other->_entries.count; i++) {
-        struct pkpy_DictEntry* entry = &c11__getitem(struct pkpy_DictEntry, &other->_entries, i);
-        if(pkpy_Var__is_null(&entry->key)) continue;
-        pkpy_Dict__set(self, entry->key, entry->val);
-    }
-}
-
-void pkpy_Dict__clear(pkpy_Dict *self) {
-    self->count = 0;
-    self->_entries.count = 0;
-    memset(self->_hashtable, 0xff, pkpy_Dict__ht_byte_size(self));
-}
-
-static int pkpy_Dict__next_entry_idx(const pkpy_Dict* self, int idx) {
-    while (idx < self->_entries.count) {
-        struct pkpy_DictEntry* entry = &c11__getitem(struct pkpy_DictEntry, &self->_entries, idx);
-        if(!pkpy_Var__is_null(&entry->key)) break;
-        idx++;
-    }
-    return idx;
-}
-
-pkpy_DictIter pkpy_Dict__iter(const pkpy_Dict *self) {
-    return (pkpy_DictIter){
-        ._dict = self,
-        ._index = pkpy_Dict__next_entry_idx(self, 0),
-    };
-}
-
-bool pkpy_DictIter__next(pkpy_DictIter *self, py_TValue *key, py_TValue *val) {
-    if(self->_index >= self->_dict->_entries.count) return false;
-    
-    struct pkpy_DictEntry* entry = &c11__getitem(struct pkpy_DictEntry, &self->_dict->_entries, self->_index);
-    if(pkpy_Var__is_null(&entry->key)) return false;
-    if (key) *key = entry->key;
-    if (val) *val = entry->val;
-
-    self->_index = pkpy_Dict__next_entry_idx(self->_dict, self->_index + 1);
-    return true;
-}

+ 6 - 0
src/public/error.c

@@ -47,4 +47,10 @@ bool py_exception(const char* name, const char* fmt, ...) {
     vm->last_exception = *py_retval();
 
     return false;
+}
+
+bool KeyError(py_Ref key){
+    if(!py_repr(key)) return false;
+    c11_sv message = py_tosv(py_retval());
+    return py_exception("KeyError", "%q", message);
 }

+ 35 - 2
src/public/modules.c

@@ -12,7 +12,7 @@ py_Ref py_getmodule(const char* name) {
 
 py_Ref py_newmodule(const char* name, const char* package) {
     pk_ManagedHeap* heap = &pk_current_vm->heap;
-    PyObject* obj = pk_ManagedHeap__gcnew(heap, tp_module, -1, 0);
+    PyObject* obj = pk_ManagedHeap__new(heap, tp_module, -1, 0);
 
     py_Ref r0 = py_pushtmp();
     py_Ref r1 = py_pushtmp();
@@ -48,7 +48,7 @@ py_Ref py_newmodule(const char* name, const char* package) {
     if(exists) abort();
     pk_NameDict__set(&pk_current_vm->modules, py_name(name), *r0);
 
-    py_poptmp(2);
+    py_shrink(2);
     return py_getmodule(name);
 }
 
@@ -148,6 +148,36 @@ static bool _py_builtins__sorted(int argc, py_Ref argv) {
     return true;
 }
 
+static bool _py_builtins__hash(int argc, py_Ref argv) {
+    PY_CHECK_ARGC(1);
+    py_i64 val;
+    if(!py_hash(argv, &val)) return false;
+    py_newint(py_retval(), val);
+    return true;
+}
+
+static bool _py_builtins__abs(int argc, py_Ref argv) {
+    PY_CHECK_ARGC(1);
+    return py_callmagic(__abs__, 1, argv);
+}
+
+static bool _py_builtins__sum(int argc, py_Ref argv) {
+    PY_CHECK_ARGC(1);
+    int length;
+    py_TValue* p = pk_arrayview(argv, &length);
+    if(!p) return TypeError("sum() expects a list or tuple");
+
+    py_Ref tmp = py_pushtmp();
+    py_newint(tmp, 0);
+    for(int i = 0; i < length; i++) {
+        if(!py_binaryadd(tmp, &p[i])) return false;
+        *tmp = *py_retval();
+    }
+
+    *py_retval() = *tmp;
+    return true;
+}
+
 py_TValue pk_builtins__register() {
     py_Ref builtins = py_newmodule("builtins", NULL);
     py_bindnativefunc(builtins, "repr", _py_builtins__repr);
@@ -157,6 +187,9 @@ py_TValue pk_builtins__register() {
     py_bindnativefunc(builtins, "hex", _py_builtins__hex);
     py_bindnativefunc(builtins, "iter", _py_builtins__iter);
     py_bindnativefunc(builtins, "next", _py_builtins__next);
+    py_bindnativefunc(builtins, "hash", _py_builtins__hash);
+    py_bindnativefunc(builtins, "abs", _py_builtins__abs);
+    py_bindnativefunc(builtins, "sum", _py_builtins__sum);
 
     py_bind(builtins, "sorted(iterable, key=None, reverse=False)", _py_builtins__sorted);
     return *builtins;

+ 5 - 10
src/public/py_array.c

@@ -33,17 +33,15 @@ int pk_arrayeq(py_TValue* lhs, int lhs_length, py_TValue* rhs, int rhs_length) {
     return true;
 }
 
-static bool _py_array_iterator__new__(int argc, py_Ref argv) {
-    PY_CHECK_ARGC(2);
+bool pk_arrayiter(py_Ref val) {
     int length;
-    py_TValue* p = pk_arrayview(py_arg(1), &length);
-    if(!p) return TypeError("expected list or tuple, got %t", py_arg(1)->type);
+    py_TValue* p = pk_arrayview(val, &length);
+    if(!p) return TypeError("expected list or tuple, got %t", val->type);
     array_iterator* ud = py_newobject(py_retval(), tp_array_iterator, 1, sizeof(array_iterator));
     ud->p = p;
     ud->length = length;
     ud->index = 0;
-    // keep a reference to the object
-    py_setslot(py_retval(), 0, py_arg(1));
+    py_setslot(py_retval(), 0, val);  // keep a reference to the object
     return true;
 }
 
@@ -65,10 +63,7 @@ static bool _py_array_iterator__next__(int argc, py_Ref argv) {
 
 py_Type pk_array_iterator__register() {
     py_Type type = pk_newtype("array_iterator", tp_object, NULL, NULL, false, true);
-
-    py_bindmagic(type, __new__, _py_array_iterator__new__);
     py_bindmagic(type, __iter__, _py_array_iterator__iter__);
     py_bindmagic(type, __next__, _py_array_iterator__next__);
-
     return type;
-}
+}

+ 459 - 32
src/public/py_dict.c

@@ -1,26 +1,39 @@
 #include "pocketpy/pocketpy.h"
 
 #include "pocketpy/common/utils.h"
+#include "pocketpy/common/sstream.h"
 #include "pocketpy/objects/object.h"
 #include "pocketpy/interpreter/vm.h"
 
+#define PK_DICT_MAX_COLLISION 3
+
 typedef struct {
+    py_i64 hash;
     py_TValue key;
     py_TValue val;
 } DictEntry;
 
+typedef struct {
+    int _[PK_DICT_MAX_COLLISION];
+} DictIndex;
+
 typedef struct {
     int length;
     int capacity;
-    int* indices;
+    DictIndex* indices;
     c11_vector /*T=DictEntry*/ entries;
 } Dict;
 
-static void Dict__ctor(Dict* self) {
+typedef struct {
+    DictEntry* curr;
+    DictEntry* end;
+} DictIterator;
+
+static void Dict__ctor(Dict* self, int capacity) {
     self->length = 0;
-    self->capacity = 16;
-    self->indices = malloc(self->capacity * sizeof(int));
-    memset(self->indices, -1, self->capacity * sizeof(int));
+    self->capacity = capacity;
+    self->indices = malloc(self->capacity * sizeof(DictIndex));
+    memset(self->indices, -1, self->capacity * sizeof(DictIndex));
     c11_vector__ctor(&self->entries, sizeof(DictEntry));
 }
 
@@ -31,75 +44,489 @@ static void Dict__dtor(Dict* self) {
     c11_vector__dtor(&self->entries);
 }
 
-static bool Dict__probe(Dict* self, py_Ref key, DictEntry* out) {
+static bool Dict__try_get(Dict* self, py_TValue* key, DictEntry** out) {
+    py_i64 hash;
+    if(!py_hash(key, &hash)) return false;
+    int idx = hash & (self->capacity - 1);
+    for(int i = 0; i < PK_DICT_MAX_COLLISION; i++) {
+        int idx2 = self->indices[idx]._[i];
+        if(idx2 == -1) continue;
+        DictEntry* entry = c11__at(DictEntry, &self->entries, idx2);
+        int res = py_eq(&entry->key, key);
+        if(res == 1) {
+            *out = entry;
+            return true;
+        }
+        if(res == -1) return false;  // error
+    }
+    *out = NULL;
+    return true;
+}
+
+static void Dict__clear(Dict* self) {
+    memset(self->indices, -1, self->capacity * sizeof(DictIndex));
+    c11_vector__clear(&self->entries);
+    self->length = 0;
+}
+
+static void Dict__rehash_2x(Dict* self) {
+    Dict old_dict = *self;
+
+    int new_capacity = self->capacity * 2;
+    assert(new_capacity <= 1073741824);
+
+    do {
+        Dict__ctor(self, new_capacity);
+
+        for(int i = 0; i < old_dict.entries.count; i++) {
+            DictEntry* entry = c11__at(DictEntry, &old_dict.entries, i);
+            if(py_isnil(&entry->key)) continue;
+            int idx = entry->hash & (new_capacity - 1);
+            bool success = false;
+            for(int i = 0; i < PK_DICT_MAX_COLLISION; i++) {
+                int idx2 = self->indices[idx]._[i];
+                if(idx2 == -1) {
+                    // insert new entry (empty slot)
+                    c11_vector__push(DictEntry, &self->entries, *entry);
+                    self->indices[idx]._[i] = self->entries.count - 1;
+                    self->length++;
+                    success = true;
+                    break;
+                }
+            }
+            if(!success) {
+                Dict__dtor(self);
+                new_capacity *= 2;
+                continue;
+            }
+        }
+        // resize complete
+        Dict__dtor(&old_dict);
+        return;
+    } while(1);
+}
+
+static void Dict__compact_entries(Dict* self) {
+    int* mappings = malloc(self->entries.count * sizeof(int));
+
+    int n = 0;
+    for(int i = 0; i < self->entries.count; i++) {
+        DictEntry* entry = c11__at(DictEntry, &self->entries, i);
+        if(py_isnil(&entry->key)) continue;
+        mappings[i] = n;
+        if(i != n) {
+            DictEntry* new_entry = c11__at(DictEntry, &self->entries, n);
+            *new_entry = *entry;
+        }
+        n++;
+    }
+    self->entries.count = n;
+    // update indices
+    for(int i = 0; i < self->capacity; i++) {
+        for(int j = 0; j < PK_DICT_MAX_COLLISION; j++) {
+            int idx = self->indices[i]._[j];
+            if(idx == -1) continue;
+            self->indices[i]._[j] = mappings[idx];
+        }
+    }
+    free(mappings);
+}
+
+static bool Dict__set(Dict* self, py_TValue* key, py_TValue* val) {
     py_i64 hash;
     if(!py_hash(key, &hash)) return false;
-    int mask = self->capacity - 1;
-    for(int idx = hash & mask;; idx = (idx + 1) & mask) {
-        int idx2 = self->indices[idx];
-        DictEntry* slot = c11__at(DictEntry, &self->entries, idx2);
-        if(slot){
-            int res = py_eq(key, &slot->key);
-            if(res == -1) return false;
-            return res;
-        }else{
-            
+    int idx = hash & (self->capacity - 1);
+    for(int i = 0; i < PK_DICT_MAX_COLLISION; i++) {
+        int idx2 = self->indices[idx]._[i];
+        if(idx2 == -1) {
+            // insert new entry
+            DictEntry* new_entry = c11_vector__emplace(&self->entries);
+            new_entry->hash = hash;
+            new_entry->key = *key;
+            new_entry->val = *val;
+            self->indices[idx]._[i] = self->entries.count - 1;
+            self->length++;
+            return true;
+        }
+        // update existing entry
+        DictEntry* entry = c11__at(DictEntry, &self->entries, idx2);
+        int res = py_eq(&entry->key, key);
+        if(res == 1) {
+            entry->val = *val;
+            return true;
         }
+        if(res == -1) return false;  // error
     }
+    // no empty slot found
+    Dict__rehash_2x(self);
+    return Dict__set(self, key, val);
 }
 
+/// Delete an entry from the dict.
+/// If the key is found, `py_retval()` is set to the value.
+/// If the key is not found, `py_retval()` is set to `nil`.
+/// Returns false on error.
+static bool Dict__pop(Dict* self, py_Ref key) {
+    py_i64 hash;
+    if(!py_hash(key, &hash)) return false;
+    int idx = hash & (self->capacity - 1);
+    for(int i = 0; i < PK_DICT_MAX_COLLISION; i++) {
+        int idx2 = self->indices[idx]._[i];
+        if(idx2 == -1) continue;
+        DictEntry* entry = c11__at(DictEntry, &self->entries, idx2);
+        int res = py_eq(&entry->key, key);
+        if(res == 1) {
+            *py_retval() = entry->val;
+            py_newnil(&entry->key);
+            self->indices[idx]._[i] = -1;
+            self->length--;
+            if(self->length < self->entries.count / 2) Dict__compact_entries(self);
+            return true;
+        }
+        if(res == -1) return false;  // error
+    }
+    py_newnil(py_retval());
+    return true;
+}
+
+static void DictIterator__ctor(DictIterator* self, Dict* dict) {
+    self->curr = dict->entries.data;
+    self->end = self->curr + dict->entries.count;
+}
+
+static DictEntry* DictIterator__next(DictIterator* self) {
+    DictEntry* retval;
+    do {
+        if(self->curr == self->end) return NULL;
+        retval = self->curr++;
+    } while(py_isnil(&retval->key));
+    return retval;
+}
+
+///////////////////////////////
 static bool _py_dict__new__(int argc, py_Ref argv) {
     py_newdict(py_retval());
     return true;
 }
 
-static bool _py_dict__getitem__(int argc, py_Ref argv){
-    PY_CHECK_ARGC(2);
-    py_i64 hash;
-    if(!py_hash(py_arg(1), &hash)) return false;
+static bool _py_dict__init__(int argc, py_Ref argv) {
+    if(argc > 2) return TypeError("dict.__init__() takes at most 2 arguments (%d given)", argc);
+    if(argc == 1) return true;
+    assert(argc == 2);
+    PY_CHECK_ARG_TYPE(1, tp_list);
     Dict* self = py_touserdata(argv);
+    py_Ref list = py_arg(1);
+    for(int i = 0; i < py_list__len(list); i++) {
+        py_Ref tuple = py_list__getitem(list, i);
+        if(!py_istuple(tuple) || py_tuple__len(tuple) != 2) {
+            return TypeError("dict.__init__() argument must be a list of tuple-2");
+        }
+        py_Ref key = py_tuple__getitem(tuple, 0);
+        py_Ref val = py_tuple__getitem(tuple, 1);
+        if(!Dict__set(self, key, val)) return false;
+    }
+    return true;
+}
 
+static bool _py_dict__getitem__(int argc, py_Ref argv) {
+    PY_CHECK_ARGC(2);
+    Dict* self = py_touserdata(argv);
+    DictEntry* entry;
+    if(!Dict__try_get(self, py_arg(1), &entry)) return false;
+    if(entry) {
+        *py_retval() = entry->val;
+        return true;
+    }
+    return KeyError(py_arg(1));
 }
 
-static bool _py_dict__setitem__(int argc, py_Ref argv){
+static bool _py_dict__setitem__(int argc, py_Ref argv) {
     PY_CHECK_ARGC(3);
-    py_i64 hash;
-    if(!py_hash(py_arg(1), &hash)) return false;
+    Dict* self = py_touserdata(argv);
+    return Dict__set(self, py_arg(1), py_arg(2));
 }
 
-static bool _py_dict__delitem__(int argc, py_Ref argv){
+static bool _py_dict__delitem__(int argc, py_Ref argv) {
     PY_CHECK_ARGC(2);
-    py_i64 hash;
-    if(!py_hash(py_arg(1), &hash)) return false;
+    Dict* self = py_touserdata(argv);
+    if(!Dict__pop(self, py_arg(1))) return false;
+    py_newnone(py_retval());
+    return true;
 }
 
-static bool _py_dict__contains__(int argc, py_Ref argv){
+static bool _py_dict__contains__(int argc, py_Ref argv) {
     PY_CHECK_ARGC(2);
-    py_i64 hash;
-    if(!py_hash(py_arg(1), &hash)) return false;
+    Dict* self = py_touserdata(argv);
+    DictEntry* entry;
+    if(!Dict__try_get(self, py_arg(1), &entry)) return false;
+    py_newbool(py_retval(), entry != NULL);
+    return true;
 }
 
-static bool _py_dict__len__(int argc, py_Ref argv){
+static bool _py_dict__len__(int argc, py_Ref argv) {
     PY_CHECK_ARGC(1);
     Dict* self = py_touserdata(argv);
     py_newint(py_retval(), self->length);
     return true;
 }
 
+static bool _py_dict__repr__(int argc, py_Ref argv) {
+    PY_CHECK_ARGC(1);
+    Dict* self = py_touserdata(argv);
+    c11_sbuf buf;
+    c11_sbuf__ctor(&buf);
+    c11_sbuf__write_char(&buf, '{');
+    bool is_first = true;
+    for(int i = 0; i < self->entries.count; i++) {
+        DictEntry* entry = c11__at(DictEntry, &self->entries, i);
+        if(py_isnil(&entry->key)) continue;
+        if(!is_first) c11_sbuf__write_cstr(&buf, ", ");
+        if(!py_repr(&entry->key)) return false;
+        c11_sbuf__write_sv(&buf, py_tosv(py_retval()));
+        c11_sbuf__write_cstr(&buf, ": ");
+        if(!py_repr(&entry->val)) return false;
+        c11_sbuf__write_sv(&buf, py_tosv(py_retval()));
+        is_first = false;
+    }
+    c11_sbuf__write_char(&buf, '}');
+    c11_string* res = c11_sbuf__submit(&buf);
+    py_newstrn(py_retval(), res->data, res->size);
+    c11_string__delete(res);
+    return true;
+}
+
+static bool _py_dict__eq__(int argc, py_Ref argv) {
+    PY_CHECK_ARGC(2);
+    Dict* self = py_touserdata(py_arg(0));
+    if(!py_isdict(py_arg(1))) {
+        py_newnotimplemented(py_retval());
+        return true;
+    }
+    Dict* other = py_touserdata(py_arg(1));
+    if(self->length != other->length) {
+        py_newbool(py_retval(), false);
+        return true;
+    }
+    DictIterator iter;
+    DictIterator__ctor(&iter, self);
+    // for each self key
+    while(1) {
+        DictEntry* entry = DictIterator__next(&iter);
+        if(!entry) break;
+        DictEntry* other_entry;
+        if(!Dict__try_get(other, &entry->key, &other_entry)) return false;
+        if(!other_entry) {
+            py_newbool(py_retval(), false);
+            return true;
+        }
+        int res = py_eq(&entry->val, &other_entry->val);
+        if(res == -1) return false;
+        if(!res) {
+            py_newbool(py_retval(), false);
+            return true;
+        }
+    }
+    return true;
+}
+
+static bool _py_dict__ne__(int argc, py_Ref argv) {
+    if(!_py_dict__eq__(argc, argv)) return false;
+    if(py_isbool(py_retval())) {
+        bool res = py_tobool(py_retval());
+        py_newbool(py_retval(), !res);
+    }
+    return true;
+}
+
+static bool _py_dict__clear(int argc, py_Ref argv) {
+    PY_CHECK_ARGC(1);
+    Dict* self = py_touserdata(argv);
+    Dict__clear(self);
+    return true;
+}
+
+static bool _py_dict__copy(int argc, py_Ref argv) {
+    PY_CHECK_ARGC(1);
+    Dict* self = py_touserdata(argv);
+    Dict* new_dict = py_newobject(py_retval(), tp_dict, 0, sizeof(Dict));
+    new_dict->capacity = self->capacity;
+    new_dict->length = self->length;
+    new_dict->entries = c11_vector__copy(&self->entries);
+    // copy indices
+    new_dict->indices = malloc(new_dict->capacity * sizeof(DictIndex));
+    memcpy(new_dict->indices, self->indices, new_dict->capacity * sizeof(DictIndex));
+    return true;
+}
+
+static bool _py_dict__update(int argc, py_Ref argv) {
+    PY_CHECK_ARGC(2);
+    PY_CHECK_ARG_TYPE(1, tp_dict);
+    Dict* self = py_touserdata(argv);
+    Dict* other = py_touserdata(py_arg(1));
+    for(int i = 0; i < other->entries.count; i++) {
+        DictEntry* entry = c11__at(DictEntry, &other->entries, i);
+        if(py_isnil(&entry->key)) continue;
+        if(!Dict__set(self, &entry->key, &entry->val)) return false;
+    }
+    return true;
+}
+
+static bool _py_dict__get(int argc, py_Ref argv) {
+    Dict* self = py_touserdata(argv);
+    if(argc > 3) return TypeError("get() takes at most 3 arguments (%d given)", argc);
+    py_Ref default_val = argc == 3 ? py_arg(2) : py_None;
+    DictEntry* entry;
+    if(!Dict__try_get(self, py_arg(1), &entry)) return false;
+    *py_retval() = entry ? entry->val : *default_val;
+    return true;
+}
+
+static bool _py_dict__pop(int argc, py_Ref argv) {
+    Dict* self = py_touserdata(argv);
+    if(argc < 2 || argc > 3) return TypeError("pop() takes 2 or 3 arguments (%d given)", argc);
+    py_Ref default_val = argc == 3 ? py_arg(2) : py_None;
+    if(!Dict__pop(self, py_arg(1))) return false;
+    if(py_isnil(py_retval())) *py_retval() = *default_val;
+    return true;
+}
+
+static bool _py_dict__items(int argc, py_Ref argv) {
+    PY_CHECK_ARGC(1);
+    Dict* self = py_touserdata(argv);
+    DictIterator* ud = py_newobject(py_retval(), tp_dict_items, 1, sizeof(DictIterator));
+    DictIterator__ctor(ud, self);
+    py_setslot(py_retval(), 0, argv);  // keep a reference to the dict
+    return true;
+}
+
+static bool _py_dict__keys(int argc, py_Ref argv) {
+    PY_CHECK_ARGC(1);
+    Dict* self = py_touserdata(argv);
+    py_newtuple(py_retval(), self->length);
+    DictIterator iter;
+    DictIterator__ctor(&iter, self);
+    int i = 0;
+    while(1) {
+        DictEntry* entry = DictIterator__next(&iter);
+        if(!entry) break;
+        py_tuple__setitem(py_retval(), i++, &entry->key);
+    }
+    assert(i == self->length);
+    return true;
+}
+
+static bool _py_dict__values(int argc, py_Ref argv) {
+    PY_CHECK_ARGC(1);
+    Dict* self = py_touserdata(argv);
+    py_newtuple(py_retval(), self->length);
+    DictIterator iter;
+    DictIterator__ctor(&iter, self);
+    int i = 0;
+    while(1) {
+        DictEntry* entry = DictIterator__next(&iter);
+        if(!entry) break;
+        py_tuple__setitem(py_retval(), i++, &entry->val);
+    }
+    assert(i == self->length);
+    return true;
+}
+
 py_Type pk_dict__register() {
     py_Type type = pk_newtype("dict", tp_object, NULL, (void (*)(void*))Dict__dtor, false, false);
 
     py_bindmagic(type, __new__, _py_dict__new__);
-    // py_bindmagic(type, __init__, _py_dict__init__);
+    py_bindmagic(type, __init__, _py_dict__init__);
     py_bindmagic(type, __getitem__, _py_dict__getitem__);
     py_bindmagic(type, __setitem__, _py_dict__setitem__);
     py_bindmagic(type, __delitem__, _py_dict__delitem__);
     py_bindmagic(type, __contains__, _py_dict__contains__);
     py_bindmagic(type, __len__, _py_dict__len__);
+    py_bindmagic(type, __repr__, _py_dict__repr__);
+    py_bindmagic(type, __eq__, _py_dict__eq__);
+    py_bindmagic(type, __ne__, _py_dict__ne__);
+
+    py_bindmethod(type, "clear", _py_dict__clear);
+    py_bindmethod(type, "copy", _py_dict__copy);
+    py_bindmethod(type, "update", _py_dict__update);
+    py_bindmethod(type, "get", _py_dict__get);
+    py_bindmethod(type, "pop", _py_dict__pop);
+    py_bindmethod(type, "items", _py_dict__items);
+    py_bindmethod(type, "keys", _py_dict__keys);
+    py_bindmethod(type, "values", _py_dict__values);
+
+    py_setdict(py_tpobject(type), __hash__, py_None);
     return type;
 }
 
+//////////////////////////
+static bool _py_dict_items__iter__(int argc, py_Ref argv) {
+    PY_CHECK_ARGC(1);
+    *py_retval() = *argv;
+    return true;
+}
+
+static bool _py_dict_items__next__(int argc, py_Ref argv) {
+    PY_CHECK_ARGC(1);
+    DictIterator* iter = py_touserdata(py_arg(0));
+    DictEntry* entry = (DictIterator__next(iter));
+    if(entry) {
+        py_newtuple(py_retval(), 2);
+        py_tuple__setitem(py_retval(), 0, &entry->key);
+        py_tuple__setitem(py_retval(), 1, &entry->val);
+        return true;
+    }
+    return StopIteration();
+}
+
+py_Type pk_dict_items__register() {
+    py_Type type = pk_newtype("dict_items", tp_object, NULL, NULL, false, true);
+    py_bindmagic(type, __iter__, _py_dict_items__iter__);
+    py_bindmagic(type, __next__, _py_dict_items__next__);
+    return type;
+}
+
+//////////////////////////
+
 void py_newdict(py_Ref out) {
     Dict* ud = py_newobject(out, tp_dict, 0, sizeof(Dict));
-    Dict__ctor(ud);
+    Dict__ctor(ud, 8);
+}
+
+py_Ref py_dict__getitem(const py_Ref self, const py_Ref key) {
+    assert(py_isdict(self));
+    Dict* ud = py_touserdata(self);
+    DictEntry* entry;
+    if(!Dict__try_get(ud, key, &entry)) return NULL;
+    if(entry) return &entry->val;
+    return NULL;
 }
+
+void py_dict__setitem(py_Ref self, const py_Ref key, const py_Ref val) {
+    assert(py_isdict(self));
+    Dict* ud = py_touserdata(self);
+    Dict__set(ud, key, val);
+}
+
+bool py_dict__contains(const py_Ref self, const py_Ref key) {
+    assert(py_isdict(self));
+    Dict* ud = py_touserdata(self);
+    DictEntry* entry;
+    bool ok = Dict__try_get(ud, key, &entry);
+    return ok && entry != NULL;
+}
+
+int py_dict__len(const py_Ref self) {
+    assert(py_isdict(self));
+    Dict* ud = py_touserdata(self);
+    return ud->length;
+}
+
+void pk_dict__mark(void* ud, void (*marker)(py_TValue*)) {
+    Dict* self = ud;
+    for(int i = 0; i < self->entries.count; i++) {
+        DictEntry* entry = c11__at(DictEntry, &self->entries, i);
+        if(py_isnil(&entry->key)) continue;
+        marker(&entry->key);
+        marker(&entry->val);
+    }
+}

+ 15 - 5
src/public/py_list.c

@@ -88,10 +88,11 @@ static bool _py_list__eq__(int argc, py_Ref argv) {
 }
 
 static bool _py_list__ne__(int argc, py_Ref argv) {
-    bool ok = _py_list__eq__(argc, argv);
-    if(!ok) return false;
-    py_Ref retval = py_retval();
-    py_newbool(retval, !py_tobool(retval));
+    if(!_py_list__eq__(argc, argv)) return false;
+    if(py_isbool(py_retval())) {
+        bool res = py_tobool(py_retval());
+        py_newbool(py_retval(), !res);
+    }
     return true;
 }
 
@@ -396,7 +397,7 @@ static bool _py_list__sort(int argc, py_Ref argv) {
 
 static bool _py_list__iter__(int argc, py_Ref argv) {
     PY_CHECK_ARGC(1);
-    return py_tpcall(tp_array_iterator, 1, argv);
+    return pk_arrayiter(argv);
 }
 
 py_Type pk_list__register() {
@@ -429,5 +430,14 @@ py_Type pk_list__register() {
     py_bindmethod(type, "sort", _py_list__sort);
 
     py_bind(py_tpobject(type), "sort(self, key=None, reverse=False)", _py_list__sort);
+
+    py_setdict(py_tpobject(type), __hash__, py_None);
     return type;
+}
+
+void pk_list__mark(void* ud, void (*marker)(py_TValue*)){
+    List* self = ud;
+    for(int i = 0; i < self->count; i++) {
+        marker(c11__at(py_TValue, self, i));
+    }
 }

+ 18 - 0
src/public/py_number.c

@@ -241,6 +241,20 @@ static bool _py_float__hash__(int argc, py_Ref argv) {
     return true;
 }
 
+static bool _py_int__abs__(int argc, py_Ref argv) {
+    PY_CHECK_ARGC(1);
+    py_i64 val = py_toint(&argv[0]);
+    py_newint(py_retval(), val < 0 ? -val : val);
+    return true;
+}
+
+static bool _py_float__abs__(int argc, py_Ref argv) {
+    PY_CHECK_ARGC(1);
+    py_f64 val = py_tofloat(&argv[0]);
+    py_newfloat(py_retval(), val < 0 ? -val : val);
+    return true;
+}
+
 static bool _py_int__new__(int argc, py_Ref argv) {
     if(argc == 1 + 0) {
         // int() == 0
@@ -431,6 +445,10 @@ void pk_number__register() {
     py_bindmagic(tp_int, __hash__, _py_int__hash__);
     py_bindmagic(tp_float, __hash__, _py_float__hash__);
 
+    // __abs__
+    py_bindmagic(tp_int, __abs__, _py_int__abs__);
+    py_bindmagic(tp_float, __abs__, _py_float__abs__);
+
     // __new__
     py_bindmagic(tp_int, __new__, _py_int__new__);
     py_bindmagic(tp_float, __new__, _py_float__new__);

+ 23 - 3
src/public/py_ops.c

@@ -48,6 +48,7 @@ bool py_hash(const py_Ref val, int64_t* out) {
     pk_TypeInfo* types = (pk_TypeInfo*)pk_current_vm->types.data;
     do {
         py_Ref _hash = &types[t].magic[__hash__];
+        if(py_isnone(_hash)) break;
         py_Ref _eq = &types[t].magic[__eq__];
         if(!py_isnil(_hash) && !py_isnil(_eq)) {
             bool ok = py_call(_hash, 1, val);
@@ -82,11 +83,30 @@ bool py_setattr(py_Ref self, py_Name name, const py_Ref val) { return false; }
 
 bool py_delattr(py_Ref self, py_Name name) { return false; }
 
-bool py_getitem(const py_Ref self, const py_Ref key, py_Ref out) { return -1; }
+bool py_getitem(const py_Ref self, const py_Ref key) {
+    py_push(self);
+    py_push(key);
+    bool ok = py_callmagic(__getitem__, 2, py_peek(-2));
+    py_shrink(2);
+    return ok;
+}
 
-bool py_setitem(py_Ref self, const py_Ref key, const py_Ref val) { return -1; }
+bool py_setitem(py_Ref self, const py_Ref key, const py_Ref val) {
+    py_push(self);
+    py_push(key);
+    py_push(val);
+    bool ok = py_callmagic(__setitem__, 3, py_peek(-3));
+    py_shrink(3);
+    return ok;
+}
 
-bool py_delitem(py_Ref self, const py_Ref key) { return -1; }
+bool py_delitem(py_Ref self, const py_Ref key) {
+    py_push(self);
+    py_push(key);
+    bool ok = py_callmagic(__delitem__, 2, py_peek(-2));
+    py_shrink(2);
+    return ok;
+}
 
 #define COMPARE_OP_IMPL(name, op, rop)                                                             \
     int py_##name(const py_Ref lhs, const py_Ref rhs) {                                            \

+ 4 - 11
src/public/py_str.c

@@ -162,7 +162,10 @@ static bool _py_str__repr__(int argc, py_Ref argv) {
 
 static bool _py_str__iter__(int argc, py_Ref argv) {
     PY_CHECK_ARGC(1);
-    return py_tpcall(tp_str_iterator, 1, argv);
+    int* ud = py_newobject(py_retval(), tp_str_iterator, 1, sizeof(int));
+    *ud = 0;
+    py_setslot(py_retval(), 0, argv);  // keep a reference to the string
+    return true;
 }
 
 static bool _py_str__getitem__(int argc, py_Ref argv) {
@@ -504,15 +507,6 @@ py_Type pk_str__register() {
     return type;
 }
 
-static bool _py_str_iterator__new__(int argc, py_Ref argv) {
-    PY_CHECK_ARGC(2);
-    PY_CHECK_ARG_TYPE(1, tp_str);
-    int* ud = py_newobject(py_retval(), tp_str_iterator, 1, sizeof(int));
-    *ud = 0;
-    py_setslot(py_retval(), 0, &argv[1]);
-    return true;
-}
-
 static bool _py_str_iterator__iter__(int argc, py_Ref argv) {
     PY_CHECK_ARGC(1);
     *py_retval() = argv[0];
@@ -535,7 +529,6 @@ static bool _py_str_iterator__next__(int argc, py_Ref argv) {
 py_Type pk_str_iterator__register() {
     py_Type type = pk_newtype("str_iterator", tp_object, NULL, NULL, false, true);
 
-    py_bindmagic(type, __new__, _py_str_iterator__new__);
     py_bindmagic(type, __iter__, _py_str_iterator__iter__);
     py_bindmagic(type, __next__, _py_str_iterator__next__);
     return type;

+ 6 - 5
src/public/py_tuple.c

@@ -113,16 +113,17 @@ static bool _py_tuple__eq__(int argc, py_Ref argv) {
 }
 
 static bool _py_tuple__ne__(int argc, py_Ref argv) {
-    bool ok = _py_tuple__eq__(argc, argv);
-    if(!ok) return false;
-    py_Ref retval = py_retval();
-    py_newbool(retval, !py_tobool(retval));
+    if(!_py_tuple__eq__(argc, argv)) return false;
+    if(py_isbool(py_retval())) {
+        bool res = py_tobool(py_retval());
+        py_newbool(py_retval(), !res);
+    }
     return true;
 }
 
 static bool _py_tuple__iter__(int argc, py_Ref argv) {
     PY_CHECK_ARGC(1);
-    return py_tpcall(tp_array_iterator, 1, argv);
+    return pk_arrayiter(argv);
 }
 
 py_Type pk_tuple__register() {

+ 16 - 4
src/public/vm.c

@@ -10,12 +10,27 @@
 #include <stdint.h>
 
 pk_VM* pk_current_vm;
+
+py_GlobalRef py_True;
+py_GlobalRef py_False;
+py_GlobalRef py_None;
+
 static pk_VM pk_default_vm;
 
 void py_initialize() {
     pk_MemoryPools__initialize();
     py_Name__initialize();
     pk_current_vm = &pk_default_vm;
+
+    // initialize some convenient references
+    static py_TValue _True, _False, _None;
+    py_newbool(&_True, true);
+    py_newbool(&_False, false);
+    py_newnone(&_None);
+    py_True = &_True;
+    py_False = &_False;
+    py_None = &_None;
+
     pk_VM__ctor(&pk_default_vm);
 }
 
@@ -190,14 +205,11 @@ bool py_call(py_Ref f, int argc, py_Ref argv) {
     if(f->type == tp_nativefunc) {
         return f->_cfunc(argc, argv);
     } else {
-        pk_VM* vm = pk_current_vm;
         py_push(f);
         py_pushnil();
         for(int i = 0; i < argc; i++)
             py_push(py_offset(argv, i));
-        pk_FrameResult res = pk_VM__vectorcall(vm, argc, 0, false);
-        assert(res == RES_ERROR || res == RES_RETURN);
-        return res == RES_RETURN;
+        return py_vectorcall(argc, 0);
     }
 }
 

+ 6 - 0
tests/00_tmp.py

@@ -0,0 +1,6 @@
+# dict delete test
+data = []
+j = 6
+for i in range(65535):
+    j = ((j*5+1) % 65535)
+    data.append(str(j))

+ 4 - 0
tests/01_int.py

@@ -107,6 +107,10 @@ assert 1 < 2 < 3
 assert 4 > 3 >= 3
 assert not 1 < 2 > 3
 
+assert abs(0) == 0
+assert abs(1) == 1
+assert abs(-1) == 1
+
 # try:
 #     1 // 0
 #     exit(1)

+ 4 - 0
tests/02_float.py

@@ -83,6 +83,10 @@ assert -2e-3 == -0.002
 assert 3.4e-3 == 0.0034
 assert 3.4e+3 == 3400.0
 
+assert abs(1.0) == 1.0
+assert abs(-1.0) == 1.0
+assert abs(0.0) == 0.0
+
 # import math
 # assert math.isnan(0/0)
 

+ 15 - 57
tests/08_dict.py

@@ -5,12 +5,15 @@ assert tinydict['Name'] == 'Tom';assert tinydict['Age'] == 7
 tinydict['Age'] = 8;tinydict['School'] = "aaa"
 assert tinydict['Age'] == 8;assert tinydict['School'] == "aaa"
 del tinydict['Name']
+
+assert repr(tinydict) == "{'Age': 8, 'Class': 'First', 'School': 'aaa'}"
 assert len(tinydict) == 3
 tinydict.clear()
 assert len(tinydict) == 0
 
 dict1 = {'user':'circle','num':[1,2,3]}
 dict2 = dict1.copy()
+
 for k,v in dict1.items():
     assert dict2[k] == v
 for t in dict1.items():
@@ -62,20 +65,6 @@ assert a == {3: 4}
 assert a.pop(3) == 4
 assert a == {}
 
-# unpacking builder
-a = {1:2, 3:4}
-b = {**a, 5:6, **a}
-assert b == {1: 2, 3: 4, 5: 6}
-
-a = {}
-b = {**a, 1:2, 3:4}
-assert b == {1: 2, 3: 4}
-
-a = {1:2, 3:4, 7:8}
-b = {**a, 1:5, 3:6}
-c = {**a, **b}
-assert c == {1: 5, 3: 6, 7: 8}
-
 a = {}
 for i in range(1000):
     a[i] = i
@@ -84,15 +73,6 @@ for i in range(1000):
     del a[i]
 assert len(a) == 0
 
-a = {
-    str(i): i
-    for i in range(10)
-}
-
-for i, s in enumerate(a):
-    assert s == str(i)
-
-
 a = {'g': 0}
 
 a['ball_3'] = 0
@@ -130,35 +110,11 @@ for i in range(len(data)):
         y = b.pop()
         del a[y]
 
-# namedict delete test
-class A: pass
-a = A()
-b = ['0', '1']
-
-for i in range(len(data)):
-    z = data[i]
-    setattr(a, str(z), i)
-    b.append(z)
-    if i % 3 == 0:
-        y = b.pop()
-        delattr(a, y)
-
 a = {1: 2, 3: 4}
 assert a.pop(1) == 2
-try:
-    a.pop(1)
-    exit(1)
-except KeyError:
-    pass
 
 assert a.pop(1, None) is None
 
-try:
-    a.pop(1, 2, 3)
-    exit(1)
-except TypeError:
-    pass
-
 n = 2 ** 17
 a = {}
 for i in range(n):
@@ -170,13 +126,15 @@ for i in range(n):
 for i in range(n):
     del a[str(i)]
 
-a = {1: 2, 3: 4}
-a['a'] = a
-assert repr(a) == "{1: 2, 3: 4, 'a': {...}}"
-
-# test gc
-import gc
-gc.collect()
-for k, v in a.items():
-    pass
-assert gc.collect() == 1
+# namedict delete test
+# class A: pass
+# a = A()
+# b = ['0', '1']
+
+# for i in range(len(data)):
+#     z = data[i]
+#     setattr(a, str(z), i)
+#     b.append(z)
+#     if i % 3 == 0:
+#         y = b.pop()
+#         delattr(a, y)

+ 12 - 1
tests/09_dictcomp.py → tests/08_dictcomp.py

@@ -1,3 +1,6 @@
+a = {i: j for i, j in [(1, 2), (3, 4)]}
+assert a == {1: 2, 3: 4}
+
 a = {i: i**2 for i in range(10)}
 assert a == {0: 0, 1: 1, 2: 4, 3: 9, 4: 16, 5: 25, 6: 36, 7: 49, 8: 64, 9: 81}
 
@@ -5,4 +8,12 @@ a = {i: i**2 for i in range(10) if i % 2 == 0}
 assert a == {0: 0, 2: 4, 4: 16, 6: 36, 8: 64}
 
 b = {k:v for k,v in a.items()}
-assert b == a
+assert b == a
+
+# a = {
+#     str(i): i
+#     for i in range(10)
+# }
+
+# for i, s in enumerate(a):
+#     assert s == str(i)

+ 0 - 0
tests/08_set.py → tests/69_set.py


+ 0 - 0
tests/09_setcomp.py → tests/69_setcomp.py