1
0
blueloveTH 2 жил өмнө
parent
commit
f3ac21ccc2
51 өөрчлөгдсөн 2023 нэмэгдсэн , 1865 устгасан
  1. 1 1
      .gitignore
  2. 1 1
      amalgamate.py
  3. 11 3
      build.py
  4. 1 1
      compile_flags.txt
  5. 20 0
      include/pocketpy/base64.h
  6. 0 0
      include/pocketpy/c.pyi
  7. 0 0
      include/pocketpy/cffi.h
  8. 0 0
      include/pocketpy/codeobject.h
  9. 1 1
      include/pocketpy/common.h
  10. 0 0
      include/pocketpy/compiler.h
  11. 0 0
      include/pocketpy/config.h
  12. 0 0
      include/pocketpy/dict.h
  13. 19 0
      include/pocketpy/easing.h
  14. 0 0
      include/pocketpy/easing.pyi
  15. 52 0
      include/pocketpy/error.h
  16. 0 0
      include/pocketpy/export.h
  17. 1 2
      include/pocketpy/expr.h
  18. 0 0
      include/pocketpy/frame.h
  19. 0 0
      include/pocketpy/gc.h
  20. 0 2
      include/pocketpy/io.h
  21. 64 0
      include/pocketpy/iter.h
  22. 0 0
      include/pocketpy/lexer.h
  23. 387 0
      include/pocketpy/linalg.h
  24. 0 0
      include/pocketpy/linalg.pyi
  25. 11 11
      include/pocketpy/memory.h
  26. 7 21
      include/pocketpy/namedict.h
  27. 0 0
      include/pocketpy/obj.h
  28. 0 0
      include/pocketpy/opcodes.h
  29. 176 0
      include/pocketpy/pocketpy.h
  30. 0 0
      include/pocketpy/random.h
  31. 0 0
      include/pocketpy/re.h
  32. 1 1
      include/pocketpy/repl.h
  33. 35 235
      include/pocketpy/str.h
  34. 52 0
      include/pocketpy/tuplelist.h
  35. 0 0
      include/pocketpy/vector.h
  36. 0 781
      include/pocketpy/vm.h
  37. 1 1
      preprocess.py
  38. 6 3
      scripts/loc.py
  39. 6 19
      src/base64.cpp
  40. 5 7
      src/ceval.cpp
  41. 3 15
      src/easing.cpp
  42. 16 55
      src/error.cpp
  43. 8 59
      src/iter.cpp
  44. 12 377
      src/linalg.cpp
  45. 1 1
      src/main.cpp
  46. 23 0
      src/namedict.cpp
  47. 15 171
      src/pocketpy.cpp
  48. 242 0
      src/str.cpp
  49. 55 0
      src/tuplelist.cpp
  50. 0 97
      src/tuplelist.h
  51. 790 0
      src/vm.cpp

+ 1 - 1
.gitignore

@@ -18,7 +18,7 @@ plugins/flutter/example/web/lib/pocketpy.js
 plugins/flutter/example/web/lib/pocketpy.wasm
 plugins/flutter/src/pocketpy.*
 plugins/macos/pocketpy/pocketpy.*
-src/_generated.h
+include/pocketpy/_generated.h
 profile.sh
 test
 src/httplib.h

+ 1 - 1
amalgamate.py

@@ -8,7 +8,7 @@ with open("src/opcodes.h", "rt", encoding='utf-8') as f:
 pipeline = [
 	["config.h", "common.h", "memory.h", "vector.h", "str.h", "tuplelist.h", "namedict.h", "error.h", "lexer.h"],
 	["obj.h", "dict.h", "codeobject.h", "frame.h"],
-	["gc.h", "vm.h", "ceval.h", "expr.h", "compiler.h", "repl.h"],
+	["gc.h", "vm.h", "expr.h", "compiler.h", "repl.h"],
 	["_generated.h", "cffi.h", "iter.h", "base64.h", "random.h", "re.h", "linalg.h", "easing.h", "io.h"],
 	["export.h", "pocketpy.h"]
 ]

+ 11 - 3
build.py

@@ -9,9 +9,17 @@ os.system("python3 preprocess.py")
 def DONE(code=0):
     exit(code)
 
-linux_common = "-Wfatal-errors --std=c++17 -O2 -Wall -fno-rtti -stdlib=libc++"
-linux_cmd = "clang++ -o pocketpy src/main.cpp " + linux_common
-linux_lib_cmd = "clang++ -fPIC -shared -o pocketpy.so src/tmp.cpp " + linux_common
+src_file_list = []
+for file in os.listdir("src"):
+    if file.endswith(".cpp") and file != "main.cpp" and file != "tmp.cpp":
+        src_file_list.append("src/" + file)
+
+main_src_arg = " ".join(src_file_list+["src/main.cpp"])
+tmp_src_arg = " ".join(src_file_list+["src/tmp.cpp"])
+
+linux_common = " -Wfatal-errors --std=c++17 -O2 -Wall -fno-rtti -stdlib=libc++ -Iinclude/ "
+linux_cmd = "clang++ -o pocketpy " + main_src_arg + linux_common
+linux_lib_cmd = "clang++ -fPIC -shared -o pocketpy.so " + tmp_src_arg + linux_common
 
 class LibBuildEnv:
     def __enter__(self):

+ 1 - 1
compile_flags.txt

@@ -3,4 +3,4 @@
 -W*
 -std=c++17
 -stdlib=libc++
--Isrc
+-Iinclude/

+ 20 - 0
include/pocketpy/base64.h

@@ -0,0 +1,20 @@
+#pragma once
+
+#include "common.h"
+
+#if PK_MODULE_BASE64
+
+#include "cffi.h"
+
+namespace pkpy {
+
+void add_module_base64(VM* vm);
+
+} // namespace pkpy
+
+
+#else
+
+ADD_MODULE_PLACEHOLDER(base64)
+
+#endif

+ 0 - 0
src/c.pyi → include/pocketpy/c.pyi


+ 0 - 0
src/cffi.h → include/pocketpy/cffi.h


+ 0 - 0
src/codeobject.h → include/pocketpy/codeobject.h


+ 1 - 1
src/common.h → include/pocketpy/common.h

@@ -20,7 +20,7 @@
 #include <variant>
 #include <type_traits>
 
-#define PK_VERSION				"1.0.7"
+#define PK_VERSION				"1.0.8"
 
 #include "config.h"
 

+ 0 - 0
src/compiler.h → include/pocketpy/compiler.h


+ 0 - 0
src/config.h → include/pocketpy/config.h


+ 0 - 0
src/dict.h → include/pocketpy/dict.h


+ 19 - 0
include/pocketpy/easing.h

@@ -0,0 +1,19 @@
+#pragma once
+
+#include "common.h"
+
+#if PK_MODULE_EASING
+
+#include "cffi.h"
+
+namespace pkpy{
+
+void add_module_easing(VM* vm);
+
+} // namespace pkpy
+
+#else
+
+ADD_MODULE_PLACEHOLDER(easing)
+
+#endif

+ 0 - 0
src/easing.pyi → include/pocketpy/easing.pyi


+ 52 - 0
include/pocketpy/error.h

@@ -0,0 +1,52 @@
+#pragma once
+
+#include "namedict.h"
+#include "str.h"
+#include "tuplelist.h"
+
+namespace pkpy{
+
+struct NeedMoreLines {
+    NeedMoreLines(bool is_compiling_class) : is_compiling_class(is_compiling_class) {}
+    bool is_compiling_class;
+};
+
+struct HandledException {};
+struct UnhandledException {};
+struct ToBeRaisedException {};
+
+enum CompileMode {
+    EXEC_MODE,
+    EVAL_MODE,
+    REPL_MODE,
+    JSON_MODE,
+    CELL_MODE
+};
+
+struct SourceData {
+    std::string source;
+    Str filename;
+    std::vector<const char*> line_starts;
+    CompileMode mode;
+
+    SourceData(const SourceData&) = delete;
+    SourceData& operator=(const SourceData&) = delete;
+
+    SourceData(const Str& source, const Str& filename, CompileMode mode);
+    std::pair<const char*,const char*> get_line(int lineno) const;
+    Str snapshot(int lineno, const char* cursor=nullptr);
+};
+
+struct Exception {
+    StrName type;
+    Str msg;
+    bool is_re;
+    stack<Str> stacktrace;
+
+    Exception(StrName type, Str msg): type(type), msg(msg), is_re(true) {}
+    bool match_type(StrName t) const { return this->type == t;}
+    void st_push(Str snapshot);
+    Str summary() const;
+};
+
+}   // namespace pkpy

+ 0 - 0
src/export.h → include/pocketpy/export.h


+ 1 - 2
src/expr.h → include/pocketpy/expr.h

@@ -4,8 +4,7 @@
 #include "common.h"
 #include "lexer.h"
 #include "error.h"
-#include "ceval.h"
-#include "str.h"
+#include "vm.h"
 
 namespace pkpy{
 

+ 0 - 0
src/frame.h → include/pocketpy/frame.h


+ 0 - 0
src/gc.h → include/pocketpy/gc.h


+ 0 - 2
src/io.h → include/pocketpy/io.h

@@ -1,8 +1,6 @@
 #pragma once
 
-#include "ceval.h"
 #include "cffi.h"
-#include "common.h"
 
 #if PK_ENABLE_OS
 

+ 64 - 0
include/pocketpy/iter.h

@@ -0,0 +1,64 @@
+#pragma once
+
+#include "cffi.h"
+#include "common.h"
+#include "frame.h"
+
+namespace pkpy{
+
+struct RangeIter{
+    PY_CLASS(RangeIter, builtins, "_range_iterator")
+    Range r;
+    i64 current;
+    RangeIter(Range r) : r(r), current(r.start) {}
+
+    static void _register(VM* vm, PyObject* mod, PyObject* type);
+};
+
+struct ArrayIter{
+    PY_CLASS(ArrayIter, builtins, "_array_iterator")
+    PyObject* ref;
+    PyObject** begin;
+    PyObject** end;
+    PyObject** current;
+
+    ArrayIter(PyObject* ref, PyObject** begin, PyObject** end)
+        : ref(ref), begin(begin), end(end), current(begin) {}
+
+    void _gc_mark() const{ PK_OBJ_MARK(ref); }
+    static void _register(VM* vm, PyObject* mod, PyObject* type);
+};
+
+struct StringIter{
+    PY_CLASS(StringIter, builtins, "_string_iterator")
+    PyObject* ref;
+    Str* str;
+    int index;
+
+    StringIter(PyObject* ref) : ref(ref), str(&PK_OBJ_GET(Str, ref)), index(0) {}
+
+    void _gc_mark() const{ PK_OBJ_MARK(ref); }
+
+    static void _register(VM* vm, PyObject* mod, PyObject* type);
+};
+
+struct Generator{
+    PY_CLASS(Generator, builtins, "_generator")
+    Frame frame;
+    int state;      // 0,1,2
+    List s_backup;
+
+    Generator(Frame&& frame, ArgsView buffer): frame(std::move(frame)), state(0) {
+        for(PyObject* obj: buffer) s_backup.push_back(obj);
+    }
+
+    void _gc_mark() const{
+        frame._gc_mark();
+        for(PyObject* obj: s_backup) PK_OBJ_MARK(obj);
+    }
+
+    PyObject* next(VM* vm);
+    static void _register(VM* vm, PyObject* mod, PyObject* type);
+};
+
+} // namespace pkpy

+ 0 - 0
src/lexer.h → include/pocketpy/lexer.h


+ 387 - 0
include/pocketpy/linalg.h

@@ -0,0 +1,387 @@
+#pragma once
+
+#include "common.h"
+
+#if PK_MODULE_LINALG
+
+#include "cffi.h"
+
+namespace pkpy{
+
+static constexpr float kEpsilon = 1e-4f;
+inline static bool isclose(float a, float b){ return fabsf(a - b) < kEpsilon; }
+
+struct Vec2{
+    float x, y;
+    Vec2() : x(0.0f), y(0.0f) {}
+    Vec2(float x, float y) : x(x), y(y) {}
+    Vec2(const Vec2& v) : x(v.x), y(v.y) {}
+
+    Vec2 operator+(const Vec2& v) const { return Vec2(x + v.x, y + v.y); }
+    Vec2& operator+=(const Vec2& v) { x += v.x; y += v.y; return *this; }
+    Vec2 operator-(const Vec2& v) const { return Vec2(x - v.x, y - v.y); }
+    Vec2& operator-=(const Vec2& v) { x -= v.x; y -= v.y; return *this; }
+    Vec2 operator*(float s) const { return Vec2(x * s, y * s); }
+    Vec2& operator*=(float s) { x *= s; y *= s; return *this; }
+    Vec2 operator/(float s) const { return Vec2(x / s, y / s); }
+    Vec2& operator/=(float s) { x /= s; y /= s; return *this; }
+    Vec2 operator-() const { return Vec2(-x, -y); }
+    bool operator==(const Vec2& v) const { return isclose(x, v.x) && isclose(y, v.y); }
+    bool operator!=(const Vec2& v) const { return !isclose(x, v.x) || !isclose(y, v.y); }
+    float dot(const Vec2& v) const { return x * v.x + y * v.y; }
+    float cross(const Vec2& v) const { return x * v.y - y * v.x; }
+    float length() const { return sqrtf(x * x + y * y); }
+    float length_squared() const { return x * x + y * y; }
+    Vec2 normalize() const { float l = length(); return Vec2(x / l, y / l); }
+};
+
+struct Vec3{
+    float x, y, z;
+    Vec3() : x(0.0f), y(0.0f), z(0.0f) {}
+    Vec3(float x, float y, float z) : x(x), y(y), z(z) {}
+    Vec3(const Vec3& v) : x(v.x), y(v.y), z(v.z) {}
+
+    Vec3 operator+(const Vec3& v) const { return Vec3(x + v.x, y + v.y, z + v.z); }
+    Vec3& operator+=(const Vec3& v) { x += v.x; y += v.y; z += v.z; return *this; }
+    Vec3 operator-(const Vec3& v) const { return Vec3(x - v.x, y - v.y, z - v.z); }
+    Vec3& operator-=(const Vec3& v) { x -= v.x; y -= v.y; z -= v.z; return *this; }
+    Vec3 operator*(float s) const { return Vec3(x * s, y * s, z * s); }
+    Vec3& operator*=(float s) { x *= s; y *= s; z *= s; return *this; }
+    Vec3 operator/(float s) const { return Vec3(x / s, y / s, z / s); }
+    Vec3& operator/=(float s) { x /= s; y /= s; z /= s; return *this; }
+    Vec3 operator-() const { return Vec3(-x, -y, -z); }
+    bool operator==(const Vec3& v) const { return isclose(x, v.x) && isclose(y, v.y) && isclose(z, v.z); }
+    bool operator!=(const Vec3& v) const { return !isclose(x, v.x) || !isclose(y, v.y) || !isclose(z, v.z); }
+    float dot(const Vec3& v) const { return x * v.x + y * v.y + z * v.z; }
+    Vec3 cross(const Vec3& v) const { return Vec3(y * v.z - z * v.y, z * v.x - x * v.z, x * v.y - y * v.x); }
+    float length() const { return sqrtf(x * x + y * y + z * z); }
+    float length_squared() const { return x * x + y * y + z * z; }
+    Vec3 normalize() const { float l = length(); return Vec3(x / l, y / l, z / l); }
+};
+
+struct Vec4{
+    float x, y, z, w;
+    Vec4() : x(0.0f), y(0.0f), z(0.0f), w(0.0f) {}
+    Vec4(float x, float y, float z, float w) : x(x), y(y), z(z), w(w) {}
+    Vec4(const Vec4& v) : x(v.x), y(v.y), z(v.z), w(v.w) {}
+
+    Vec4 operator+(const Vec4& v) const { return Vec4(x + v.x, y + v.y, z + v.z, w + v.w); }
+    Vec4& operator+=(const Vec4& v) { x += v.x; y += v.y; z += v.z; w += v.w; return *this; }
+    Vec4 operator-(const Vec4& v) const { return Vec4(x - v.x, y - v.y, z - v.z, w - v.w); }
+    Vec4& operator-=(const Vec4& v) { x -= v.x; y -= v.y; z -= v.z; w -= v.w; return *this; }
+    Vec4 operator*(float s) const { return Vec4(x * s, y * s, z * s, w * s); }
+    Vec4& operator*=(float s) { x *= s; y *= s; z *= s; w *= s; return *this; }
+    Vec4 operator/(float s) const { return Vec4(x / s, y / s, z / s, w / s); }
+    Vec4& operator/=(float s) { x /= s; y /= s; z /= s; w /= s; return *this; }
+    Vec4 operator-() const { return Vec4(-x, -y, -z, -w); }
+    bool operator==(const Vec4& v) const { return isclose(x, v.x) && isclose(y, v.y) && isclose(z, v.z) && isclose(w, v.w); }
+    bool operator!=(const Vec4& v) const { return !isclose(x, v.x) || !isclose(y, v.y) || !isclose(z, v.z) || !isclose(w, v.w); }
+    float dot(const Vec4& v) const { return x * v.x + y * v.y + z * v.z + w * v.w; }
+    float length() const { return sqrtf(x * x + y * y + z * z + w * w); }
+    float length_squared() const { return x * x + y * y + z * z + w * w; }
+    Vec4 normalize() const { float l = length(); return Vec4(x / l, y / l, z / l, w / l); }
+};
+
+struct Mat3x3{    
+    union {
+        struct {
+            float        _11, _12, _13;
+            float        _21, _22, _23;
+            float        _31, _32, _33;
+        };
+        float m[3][3];
+        float v[9];
+    };
+
+    Mat3x3() {}
+    Mat3x3(float _11, float _12, float _13,
+           float _21, float _22, float _23,
+           float _31, float _32, float _33)
+        : _11(_11), _12(_12), _13(_13)
+        , _21(_21), _22(_22), _23(_23)
+        , _31(_31), _32(_32), _33(_33) {}
+
+    void set_zeros(){ for (int i=0; i<9; ++i) v[i] = 0.0f; }
+    void set_ones(){ for (int i=0; i<9; ++i) v[i] = 1.0f; }
+    void set_identity(){ set_zeros(); _11 = _22 = _33 = 1.0f; }
+
+    static Mat3x3 zeros(){
+        static Mat3x3 ret(0, 0, 0, 0, 0, 0, 0, 0, 0);
+        return ret;
+    }
+
+    static Mat3x3 ones(){
+        static Mat3x3 ret(1, 1, 1, 1, 1, 1, 1, 1, 1);
+        return ret;
+    }
+
+    static Mat3x3 identity(){
+        static Mat3x3 ret(1, 0, 0, 0, 1, 0, 0, 0, 1);
+        return ret;
+    }
+
+    Mat3x3 operator+(const Mat3x3& other) const{ 
+        Mat3x3 ret;
+        for (int i=0; i<9; ++i) ret.v[i] = v[i] + other.v[i];
+        return ret;
+    }
+
+    Mat3x3 operator-(const Mat3x3& other) const{ 
+        Mat3x3 ret;
+        for (int i=0; i<9; ++i) ret.v[i] = v[i] - other.v[i];
+        return ret;
+    }
+
+    Mat3x3 operator*(float scalar) const{ 
+        Mat3x3 ret;
+        for (int i=0; i<9; ++i) ret.v[i] = v[i] * scalar;
+        return ret;
+    }
+
+    Mat3x3 operator/(float scalar) const{ 
+        Mat3x3 ret;
+        for (int i=0; i<9; ++i) ret.v[i] = v[i] / scalar;
+        return ret;
+    }
+
+    Mat3x3& operator+=(const Mat3x3& other){ 
+        for (int i=0; i<9; ++i) v[i] += other.v[i];
+        return *this;
+    }
+
+    Mat3x3& operator-=(const Mat3x3& other){ 
+        for (int i=0; i<9; ++i) v[i] -= other.v[i];
+        return *this;
+    }
+
+    Mat3x3& operator*=(float scalar){ 
+        for (int i=0; i<9; ++i) v[i] *= scalar;
+        return *this;
+    }
+
+    Mat3x3& operator/=(float scalar){ 
+        for (int i=0; i<9; ++i) v[i] /= scalar;
+        return *this;
+    }
+
+    Mat3x3 matmul(const Mat3x3& other) const{
+        Mat3x3 ret;
+        ret._11 = _11 * other._11 + _12 * other._21 + _13 * other._31;
+        ret._12 = _11 * other._12 + _12 * other._22 + _13 * other._32;
+        ret._13 = _11 * other._13 + _12 * other._23 + _13 * other._33;
+        ret._21 = _21 * other._11 + _22 * other._21 + _23 * other._31;
+        ret._22 = _21 * other._12 + _22 * other._22 + _23 * other._32;
+        ret._23 = _21 * other._13 + _22 * other._23 + _23 * other._33;
+        ret._31 = _31 * other._11 + _32 * other._21 + _33 * other._31;
+        ret._32 = _31 * other._12 + _32 * other._22 + _33 * other._32;
+        ret._33 = _31 * other._13 + _32 * other._23 + _33 * other._33;
+        return ret;
+    }
+
+    Vec3 matmul(const Vec3& other) const{
+        Vec3 ret;
+        ret.x = _11 * other.x + _12 * other.y + _13 * other.z;
+        ret.y = _21 * other.x + _22 * other.y + _23 * other.z;
+        ret.z = _31 * other.x + _32 * other.y + _33 * other.z;
+        return ret;
+    }
+
+    bool operator==(const Mat3x3& other) const{
+        for (int i=0; i<9; ++i){
+            if (!isclose(v[i], other.v[i])) return false;
+        }
+        return true;
+    }
+
+    bool operator!=(const Mat3x3& other) const{
+        for (int i=0; i<9; ++i){
+            if (!isclose(v[i], other.v[i])) return true;
+        }
+        return false;
+    }
+
+    float determinant() const{
+        return _11 * _22 * _33 + _12 * _23 * _31 + _13 * _21 * _32
+             - _11 * _23 * _32 - _12 * _21 * _33 - _13 * _22 * _31;
+    }
+
+    Mat3x3 transpose() const{
+        Mat3x3 ret;
+        ret._11 = _11;  ret._12 = _21;  ret._13 = _31;
+        ret._21 = _12;  ret._22 = _22;  ret._23 = _32;
+        ret._31 = _13;  ret._32 = _23;  ret._33 = _33;
+        return ret;
+    }
+
+    bool inverse(Mat3x3& ret) const{
+        float det = determinant();
+        if (fabsf(det) < kEpsilon) return false;
+        float inv_det = 1.0f / det;
+        ret._11 = (_22 * _33 - _23 * _32) * inv_det;
+        ret._12 = (_13 * _32 - _12 * _33) * inv_det;
+        ret._13 = (_12 * _23 - _13 * _22) * inv_det;
+        ret._21 = (_23 * _31 - _21 * _33) * inv_det;
+        ret._22 = (_11 * _33 - _13 * _31) * inv_det;
+        ret._23 = (_13 * _21 - _11 * _23) * inv_det;
+        ret._31 = (_21 * _32 - _22 * _31) * inv_det;
+        ret._32 = (_12 * _31 - _11 * _32) * inv_det;
+        ret._33 = (_11 * _22 - _12 * _21) * inv_det;
+        return true;
+    }
+
+    /*************** affine transformations ***************/
+    static Mat3x3 trs(Vec2 t, float radian, Vec2 s){
+        float cr = cosf(radian);
+        float sr = sinf(radian);
+        return Mat3x3(s.x * cr,   -s.y * sr,  t.x,
+                      s.x * sr,   s.y * cr,   t.y,
+                      0.0f,       0.0f,       1.0f);
+    }
+
+    bool is_affine() const{
+        float det = _11 * _22 - _12 * _21;
+        if(fabsf(det) < kEpsilon) return false;
+        return _31 == 0.0f && _32 == 0.0f && _33 == 1.0f;
+    }
+
+    Mat3x3 inverse_affine() const{
+        Mat3x3 ret;
+        float det = _11 * _22 - _12 * _21;
+        float inv_det = 1.0f / det;
+        ret._11 = _22 * inv_det;
+        ret._12 = -_12 * inv_det;
+        ret._13 = (_12 * _23 - _13 * _22) * inv_det;
+        ret._21 = -_21 * inv_det;
+        ret._22 = _11 * inv_det;
+        ret._23 = (_13 * _21 - _11 * _23) * inv_det;
+        ret._31 = 0.0f;
+        ret._32 = 0.0f;
+        ret._33 = 1.0f;
+        return ret;
+    }
+
+    Mat3x3 matmul_affine(const Mat3x3& other) const{
+        Mat3x3 ret;
+        ret._11 = _11 * other._11 + _12 * other._21;
+        ret._12 = _11 * other._12 + _12 * other._22;
+        ret._13 = _11 * other._13 + _12 * other._23 + _13;
+        ret._21 = _21 * other._11 + _22 * other._21;
+        ret._22 = _21 * other._12 + _22 * other._22;
+        ret._23 = _21 * other._13 + _22 * other._23 + _23;
+        ret._31 = 0.0f;
+        ret._32 = 0.0f;
+        ret._33 = 1.0f;
+        return ret;
+    }
+
+    Vec2 translation() const { return Vec2(_13, _23); }
+    float rotation() const { return atan2f(_21, _11); }
+    Vec2 scale() const {
+        return Vec2(
+            sqrtf(_11 * _11 + _21 * _21),
+            sqrtf(_12 * _12 + _22 * _22)
+        );
+    }
+
+    Vec2 transform_point(Vec2 vec) const {
+        return Vec2(_11 * vec.x + _12 * vec.y + _13, _21 * vec.x + _22 * vec.y + _23);
+    }
+
+    Vec2 transform_vector(Vec2 vec) const {
+        return Vec2(_11 * vec.x + _12 * vec.y, _21 * vec.x + _22 * vec.y);
+    }
+};
+
+struct PyVec2;
+struct PyVec3;
+struct PyVec4;
+struct PyMat3x3;
+PyObject* py_var(VM*, Vec2);
+PyObject* py_var(VM*, const PyVec2&);
+PyObject* py_var(VM*, Vec3);
+PyObject* py_var(VM*, const PyVec3&);
+PyObject* py_var(VM*, Vec4);
+PyObject* py_var(VM*, const PyVec4&);
+PyObject* py_var(VM*, const Mat3x3&);
+PyObject* py_var(VM*, const PyMat3x3&);
+
+
+struct PyVec2: Vec2 {
+    PY_CLASS(PyVec2, linalg, vec2)
+
+    PyVec2() : Vec2() {}
+    PyVec2(const Vec2& v) : Vec2(v) {}
+    PyVec2(const PyVec2& v) : Vec2(v) {}
+
+    static void _register(VM* vm, PyObject* mod, PyObject* type);
+};
+
+struct PyVec3: Vec3 {
+    PY_CLASS(PyVec3, linalg, vec3)
+
+    PyVec3() : Vec3() {}
+    PyVec3(const Vec3& v) : Vec3(v) {}
+    PyVec3(const PyVec3& v) : Vec3(v) {}
+
+    static void _register(VM* vm, PyObject* mod, PyObject* type);
+};
+
+struct PyVec4: Vec4{
+    PY_CLASS(PyVec4, linalg, vec4)
+
+    PyVec4(): Vec4(){}
+    PyVec4(const Vec4& v): Vec4(v){}
+    PyVec4(const PyVec4& v): Vec4(v){}
+
+    static void _register(VM* vm, PyObject* mod, PyObject* type);
+};
+
+struct PyMat3x3: Mat3x3{
+    PY_CLASS(PyMat3x3, linalg, mat3x3)
+
+    PyMat3x3(): Mat3x3(){}
+    PyMat3x3(const Mat3x3& other): Mat3x3(other){}
+    PyMat3x3(const PyMat3x3& other): Mat3x3(other){}
+
+    static void _register(VM* vm, PyObject* mod, PyObject* type);
+};
+
+inline PyObject* py_var(VM* vm, Vec2 obj){ return VAR_T(PyVec2, obj); }
+inline PyObject* py_var(VM* vm, const PyVec2& obj){ return VAR_T(PyVec2, obj);}
+
+inline PyObject* py_var(VM* vm, Vec3 obj){ return VAR_T(PyVec3, obj); }
+inline PyObject* py_var(VM* vm, const PyVec3& obj){ return VAR_T(PyVec3, obj);}
+
+inline PyObject* py_var(VM* vm, Vec4 obj){ return VAR_T(PyVec4, obj); }
+inline PyObject* py_var(VM* vm, const PyVec4& obj){ return VAR_T(PyVec4, obj);}
+
+inline PyObject* py_var(VM* vm, const Mat3x3& obj){ return VAR_T(PyMat3x3, obj); }
+inline PyObject* py_var(VM* vm, const PyMat3x3& obj){ return VAR_T(PyMat3x3, obj); }
+
+template<> inline Vec2 py_cast<Vec2>(VM* vm, PyObject* obj) { return CAST(PyVec2&, obj); }
+template<> inline Vec3 py_cast<Vec3>(VM* vm, PyObject* obj) { return CAST(PyVec3&, obj); }
+template<> inline Vec4 py_cast<Vec4>(VM* vm, PyObject* obj) { return CAST(PyVec4&, obj); }
+template<> inline Mat3x3 py_cast<Mat3x3>(VM* vm, PyObject* obj) { return CAST(PyMat3x3&, obj); }
+
+template<> inline Vec2 _py_cast<Vec2>(VM* vm, PyObject* obj) { return _CAST(PyVec2&, obj); }
+template<> inline Vec3 _py_cast<Vec3>(VM* vm, PyObject* obj) { return _CAST(PyVec3&, obj); }
+template<> inline Vec4 _py_cast<Vec4>(VM* vm, PyObject* obj) { return _CAST(PyVec4&, obj); }
+template<> inline Mat3x3 _py_cast<Mat3x3>(VM* vm, PyObject* obj) { return _CAST(PyMat3x3&, obj); }
+
+inline void add_module_linalg(VM* vm){
+    PyObject* linalg = vm->new_module("linalg");
+    PyVec2::register_class(vm, linalg);
+    PyVec3::register_class(vm, linalg);
+    PyVec4::register_class(vm, linalg);
+    PyMat3x3::register_class(vm, linalg);
+}
+
+static_assert(sizeof(Py_<PyMat3x3>) <= 64);
+
+}   // namespace pkpy
+
+#else
+
+ADD_MODULE_PLACEHOLDER(linalg)
+
+#endif

+ 0 - 0
src/linalg.pyi → include/pocketpy/linalg.pyi


+ 11 - 11
src/memory.h → include/pocketpy/memory.h

@@ -86,17 +86,17 @@ struct DoubleLinkedList{
         _size--;
     }
 
-    void move_all_back(DoubleLinkedList<T>& other){
-        if(other.empty()) return;
-        other.tail.prev->next = &tail;
-        tail.prev->next = other.head.next;
-        other.head.next->prev = tail.prev;
-        tail.prev = other.tail.prev;
-        _size += other._size;
-        other.head.next = &other.tail;
-        other.tail.prev = &other.head;
-        other._size = 0;
-    }
+    // void move_all_back(DoubleLinkedList<T>& other){
+    //     if(other.empty()) return;
+    //     other.tail.prev->next = &tail;
+    //     tail.prev->next = other.head.next;
+    //     other.head.next->prev = tail.prev;
+    //     tail.prev = other.tail.prev;
+    //     _size += other._size;
+    //     other.head.next = &other.tail;
+    //     other.tail.prev = &other.head;
+    //     other._size = 0;
+    // }
 
     bool empty() const {
 #if PK_DEBUG_MEMORY_POOL

+ 7 - 21
src/namedict.h → include/pocketpy/namedict.h

@@ -6,28 +6,14 @@
 
 namespace pkpy{
 
-const uint16_t kHashSeeds[] = {9629, 43049, 13267, 59509, 39251, 1249, 27689, 9719, 19913};
-
-#define _hash(key, mask, hash_seed) ( ( (key).index * (hash_seed) >> 8 ) & (mask) )
-
-inline uint16_t find_perfect_hash_seed(uint16_t capacity, const std::vector<StrName>& keys){
-    if(keys.empty()) return kHashSeeds[0];
-    static std::set<uint16_t> indices;
-    indices.clear();
-    std::pair<uint16_t, float> best_score = {kHashSeeds[0], 0.0f};
-    const int kHashSeedsSize = sizeof(kHashSeeds) / sizeof(kHashSeeds[0]);
-    for(int i=0; i<kHashSeedsSize; i++){
-        indices.clear();
-        for(auto key: keys){
-            uint16_t index = _hash(key, capacity-1, kHashSeeds[i]);
-            indices.insert(index);
-        }
-        float score = indices.size() / (float)keys.size();
-        if(score > best_score.second) best_score = {kHashSeeds[i], score};
-    }
-    return best_score.first;
+inline const uint16_t kHashSeeds[] = {9629, 43049, 13267, 59509, 39251, 1249, 27689, 9719, 19913};
+
+inline uint16_t _hash(StrName key, uint16_t mask, uint16_t hash_seed){
+    return ( (key).index * (hash_seed) >> 8 ) & (mask);
 }
 
+uint16_t _find_perfect_hash_seed(uint16_t capacity, const std::vector<StrName>& keys);
+
 template<typename T>
 struct NameDictImpl {
     using Item = std::pair<StrName, T>;
@@ -121,7 +107,7 @@ while(!_items[i].first.empty()) {       \
     }
 
     void _try_perfect_rehash(){
-        _hash_seed = find_perfect_hash_seed(_capacity, keys());
+        _hash_seed = _find_perfect_hash_seed(_capacity, keys());
         _rehash(false); // do not resize
     }
 

+ 0 - 0
src/obj.h → include/pocketpy/obj.h


+ 0 - 0
src/opcodes.h → include/pocketpy/opcodes.h


+ 176 - 0
include/pocketpy/pocketpy.h

@@ -0,0 +1,176 @@
+#pragma once
+
+#include "compiler.h"
+#include "obj.h"
+#include "repl.h"
+#include "iter.h"
+#include "base64.h"
+#include "cffi.h"
+#include "linalg.h"
+#include "easing.h"
+#include "io.h"
+#include "_generated.h"
+#include "export.h"
+#include "vm.h"
+#include "re.h"
+#include "random.h"
+
+namespace pkpy {
+
+inline CodeObject_ VM::compile(Str source, Str filename, CompileMode mode, bool unknown_global_scope) {
+    Compiler compiler(this, source, filename, mode, unknown_global_scope);
+    try{
+        return compiler.compile();
+    }catch(Exception& e){
+#if PK_DEBUG_FULL_EXCEPTION
+        std::cerr << e.summary() << std::endl;
+#endif
+        _error(e);
+        return nullptr;
+    }
+}
+
+
+void init_builtins(VM* _vm);
+
+struct PyREPL{
+    PY_CLASS(PyREPL, sys, _repl)
+
+    REPL* repl;
+
+    PyREPL(VM* vm){ repl = new REPL(vm); }
+    ~PyREPL(){ delete repl; }
+
+    PyREPL(const PyREPL&) = delete;
+    PyREPL& operator=(const PyREPL&) = delete;
+
+    PyREPL(PyREPL&& other) noexcept{
+        repl = other.repl;
+        other.repl = nullptr;
+    }
+
+    struct TempOut{
+        PrintFunc backup;
+        VM* vm;
+        TempOut(VM* vm, PrintFunc f){
+            this->vm = vm;
+            this->backup = vm->_stdout;
+            vm->_stdout = f;
+        }
+        ~TempOut(){
+            vm->_stdout = backup;
+        }
+        TempOut(const TempOut&) = delete;
+        TempOut& operator=(const TempOut&) = delete;
+        TempOut(TempOut&&) = delete;
+        TempOut& operator=(TempOut&&) = delete;
+    };
+
+    static void _register(VM* vm, PyObject* mod, PyObject* type){
+        vm->bind_constructor<1>(type, [](VM* vm, ArgsView args){
+            return VAR_T(PyREPL, vm);
+        });
+
+        vm->bind_method<1>(type, "input", [](VM* vm, ArgsView args){
+            PyREPL& self = _CAST(PyREPL&, args[0]);
+            const Str& s = CAST(Str&, args[1]);
+            static std::stringstream ss_out;
+            ss_out.str("");
+            TempOut _(vm, [](VM* vm, const Str& s){ ss_out << s; });
+            bool ok = self.repl->input(s.str());
+            return VAR(Tuple({VAR(ok), VAR(ss_out.str())}));
+        });
+    }
+};
+
+
+void add_module_timeit(VM* vm);
+void add_module_time(VM* vm);
+void add_module_sys(VM* vm);
+void add_module_json(VM* vm);
+
+void add_module_math(VM* vm);
+void add_module_dis(VM* vm);
+void add_module_traceback(VM* vm);
+void add_module_gc(VM* vm);
+
+}   // namespace pkpy
+
+/*************************GLOBAL NAMESPACE*************************/
+extern "C" {
+    PK_LEGACY_EXPORT
+    void pkpy_free(void* p){
+        free(p);
+    }
+
+    PK_LEGACY_EXPORT
+    void pkpy_vm_exec(pkpy::VM* vm, const char* source){
+        vm->exec(source, "main.py", pkpy::EXEC_MODE);
+    }
+
+    PK_LEGACY_EXPORT
+    void pkpy_vm_exec_2(pkpy::VM* vm, const char* source, const char* filename, int mode, const char* module){
+        pkpy::PyObject* mod;
+        if(module == nullptr) mod = vm->_main;
+        else{
+            mod = vm->_modules.try_get(module);
+            if(mod == nullptr) return;
+        }
+        vm->exec(source, filename, (pkpy::CompileMode)mode, mod);
+    }
+
+    PK_LEGACY_EXPORT
+    void pkpy_vm_compile(pkpy::VM* vm, const char* source, const char* filename, int mode, bool* ok, char** res){
+        try{
+            pkpy::CodeObject_ code = vm->compile(source, filename, (pkpy::CompileMode)mode);
+            *res = code->serialize(vm).c_str_dup();
+            *ok = true;
+        }catch(pkpy::Exception& e){
+            *ok = false;
+            *res = e.summary().c_str_dup();
+        }catch(std::exception& e){
+            *ok = false;
+            *res = strdup(e.what());
+        }catch(...){
+            *ok = false;
+            *res = strdup("unknown error");
+        }
+    }
+
+    PK_LEGACY_EXPORT
+    pkpy::REPL* pkpy_new_repl(pkpy::VM* vm){
+        pkpy::REPL* p = new pkpy::REPL(vm);
+        return p;
+    }
+
+    PK_LEGACY_EXPORT
+    bool pkpy_repl_input(pkpy::REPL* r, const char* line){
+        return r->input(line);
+    }
+
+    PK_LEGACY_EXPORT
+    void pkpy_vm_add_module(pkpy::VM* vm, const char* name, const char* source){
+        vm->_lazy_modules[name] = source;
+    }
+
+    PK_LEGACY_EXPORT
+    pkpy::VM* pkpy_new_vm(bool enable_os=true){
+        pkpy::VM* p = new pkpy::VM(enable_os);
+        return p;
+    }
+
+    PK_LEGACY_EXPORT
+    void pkpy_delete_vm(pkpy::VM* vm){
+        delete vm;
+    }
+
+    PK_LEGACY_EXPORT
+    void pkpy_delete_repl(pkpy::REPL* repl){
+        delete repl;
+    }
+
+    PK_LEGACY_EXPORT
+    void pkpy_vm_gc_on_delete(pkpy::VM* vm, void (*f)(pkpy::VM *, pkpy::PyObject *)){
+        vm->heap._gc_on_delete = f;
+    }
+}

+ 0 - 0
src/random.h → include/pocketpy/random.h


+ 0 - 0
src/re.h → include/pocketpy/re.h


+ 1 - 1
src/repl.h → include/pocketpy/repl.h

@@ -1,7 +1,7 @@
 #pragma once
 
 #include "compiler.h"
-#include "ceval.h"
+#include "vm.h"
 
 #ifdef _WIN32
 #define WIN32_LEAN_AND_MEAN

+ 35 - 235
src/str.h → include/pocketpy/str.h

@@ -89,31 +89,23 @@ struct Str{
     bool empty() const { return size == 0; }
     size_t hash() const{ return std::hash<std::string_view>()(sv()); }
 
-    Str& operator=(const Str& other){
-        if(!is_inlined()) pool64.dealloc(data);
-        size = other.size;
-        is_ascii = other.is_ascii;
-        _alloc();
-        memcpy(data, other.data, size);
-        return *this;
-    }
-
-    ~Str(){
-        if(!is_inlined()) pool64.dealloc(data);
-        if(_cached_c_str != nullptr) free((void*)_cached_c_str);
-    }
-
-    Str operator+(const Str& other) const {
-        Str ret(size + other.size, is_ascii && other.is_ascii);
-        memcpy(ret.data, data, size);
-        memcpy(ret.data + size, other.data, other.size);
-        return ret;
-    }
-
-    Str operator+(const char* p) const {
-        Str other(p);
-        return *this + other;
-    }
+    Str& operator=(const Str& other);
+    Str operator+(const Str& other) const;
+    Str operator+(const char* p) const;
+
+    bool operator==(const Str& other) const;
+    bool operator!=(const Str& other) const;
+    bool operator==(const std::string_view other) const;
+    bool operator!=(const std::string_view other) const;
+    bool operator==(const char* p) const;
+    bool operator!=(const char* p) const;
+    bool operator<(const Str& other) const;
+    bool operator>(const Str& other) const;
+    bool operator<=(const Str& other) const;
+    bool operator>=(const Str& other) const;
+    bool operator<(const std::string_view other) const;
+
+    ~Str();
 
     friend Str operator+(const char* p, const Str& str){
         Str other(p);
@@ -125,222 +117,30 @@ struct Str{
         return os;
     }
 
-    bool operator==(const Str& other) const {
-        if(size != other.size) return false;
-        return memcmp(data, other.data, size) == 0;
-    }
-
-    bool operator!=(const Str& other) const {
-        if(size != other.size) return true;
-        return memcmp(data, other.data, size) != 0;
-    }
-
-    bool operator==(const std::string_view other) const {
-        if(size != (int)other.size()) return false;
-        return memcmp(data, other.data(), size) == 0;
-    }
-
-    bool operator!=(const std::string_view other) const {
-        if(size != (int)other.size()) return true;
-        return memcmp(data, other.data(), size) != 0;
-    }
-
-    bool operator==(const char* p) const {
-        return *this == std::string_view(p);
-    }
-
-    bool operator!=(const char* p) const {
-        return *this != std::string_view(p);
-    }
-
-    bool operator<(const Str& other) const {
-        int ret = strncmp(data, other.data, std::min(size, other.size));
-        if(ret != 0) return ret < 0;
-        return size < other.size;
-    }
-
-    bool operator<(const std::string_view other) const {
-        int ret = strncmp(data, other.data(), std::min(size, (int)other.size()));
-        if(ret != 0) return ret < 0;
-        return size < (int)other.size();
-    }
-
     friend bool operator<(const std::string_view other, const Str& str){
         return str > other;
     }
 
-    bool operator>(const Str& other) const {
-        int ret = strncmp(data, other.data, std::min(size, other.size));
-        if(ret != 0) return ret > 0;
-        return size > other.size;
-    }
-
-    bool operator<=(const Str& other) const {
-        int ret = strncmp(data, other.data, std::min(size, other.size));
-        if(ret != 0) return ret < 0;
-        return size <= other.size;
-    }
-
-    bool operator>=(const Str& other) const {
-        int ret = strncmp(data, other.data, std::min(size, other.size));
-        if(ret != 0) return ret > 0;
-        return size >= other.size;
-    }
-
-    Str substr(int start, int len) const {
-        Str ret(len, is_ascii);
-        memcpy(ret.data, data + start, len);
-        return ret;
-    }
-
-    Str substr(int start) const {
-        return substr(start, size - start);
-    }
-
-    char* c_str_dup() const {
-        char* p = (char*)malloc(size + 1);
-        memcpy(p, data, size);
-        p[size] = 0;
-        return p;
-    }
-
-    const char* c_str(){
-        if(_cached_c_str == nullptr){
-            _cached_c_str = c_str_dup();
-        }
-        return _cached_c_str;
-    }
-
-    std::string_view sv() const {
-        return std::string_view(data, size);
-    }
-
-    std::string str() const {
-        return std::string(data, size);
-    }
-
-    Str lstrip() const {
-        std::string copy(data, size);
-        copy.erase(copy.begin(), std::find_if(copy.begin(), copy.end(), [](char c) {
-            // std::isspace(c) does not working on windows (Debug)
-            return c != ' ' && c != '\t' && c != '\r' && c != '\n';
-        }));
-        return Str(copy);
-    }
-
-    Str strip() const {
-        std::string copy(data, size);
-        copy.erase(copy.begin(), std::find_if(copy.begin(), copy.end(), [](char c) {
-            return c != ' ' && c != '\t' && c != '\r' && c != '\n';
-        }));
-        copy.erase(std::find_if(copy.rbegin(), copy.rend(), [](char c) {
-            return c != ' ' && c != '\t' && c != '\r' && c != '\n';
-        }).base(), copy.end());
-        return Str(copy);
-    }
-
-    Str lower() const{
-        std::string copy(data, size);
-        std::transform(copy.begin(), copy.end(), copy.begin(), [](unsigned char c){ return std::tolower(c); });
-        return Str(copy);
-    }
-
-    Str upper() const{
-        std::string copy(data, size);
-        std::transform(copy.begin(), copy.end(), copy.begin(), [](unsigned char c){ return std::toupper(c); });
-        return Str(copy);
-    }
-
-    Str escape(bool single_quote=true) const {
-        std::stringstream ss;
-        ss << (single_quote ? '\'' : '"');
-        for (int i=0; i<length(); i++) {
-            char c = this->operator[](i);
-            switch (c) {
-                case '"':
-                    if(!single_quote) ss << '\\';
-                    ss << '"';
-                    break;
-                case '\'':
-                    if(single_quote) ss << '\\';
-                    ss << '\'';
-                    break;
-                case '\\': ss << '\\' << '\\'; break;
-                case '\n': ss << "\\n"; break;
-                case '\r': ss << "\\r"; break;
-                case '\t': ss << "\\t"; break;
-                default:
-                    if ('\x00' <= c && c <= '\x1f') {
-                        ss << "\\x" << std::hex << std::setw(2) << std::setfill('0') << (int)c;
-                    } else {
-                        ss << c;
-                    }
-            }
-        }
-        ss << (single_quote ? '\'' : '"');
-        return ss.str();
-    }
-
-    int index(const Str& sub, int start=0) const {
-        auto p = std::search(data + start, data + size, sub.data, sub.data + sub.size);
-        if(p == data + size) return -1;
-        return p - data;
-    }
-
-    Str replace(const Str& old, const Str& new_, int count=-1) const {
-        std::stringstream ss;
-        int start = 0;
-        while(true){
-            int i = index(old, start);
-            if(i == -1) break;
-            ss << substr(start, i - start);
-            ss << new_;
-            start = i + old.size;
-            if(count != -1 && --count == 0) break;
-        }
-        ss << substr(start, size - start);
-        return ss.str();
-    }
+    Str substr(int start, int len) const;
+    Str substr(int start) const;
+    char* c_str_dup() const;
+    const char* c_str();
+    std::string_view sv() const;
+    std::string str() const;
+    Str lstrip() const;
+    Str strip() const;
+    Str lower() const;
+    Str upper() const;
+    Str escape(bool single_quote=true) const;
+    int index(const Str& sub, int start=0) const;
+    Str replace(const Str& old, const Str& new_, int count=-1) const;
 
     /*************unicode*************/
-
-    int _unicode_index_to_byte(int i) const{
-        if(is_ascii) return i;
-        int j = 0;
-        while(i > 0){
-            j += utf8len(data[j]);
-            i--;
-        }
-        return j;
-    }
-
-    int _byte_index_to_unicode(int n) const{
-        if(is_ascii) return n;
-        int cnt = 0;
-        for(int i=0; i<n; i++){
-            if((data[i] & 0xC0) != 0x80) cnt++;
-        }
-        return cnt;
-    }
-
-    Str u8_getitem(int i) const{
-        i = _unicode_index_to_byte(i);
-        return substr(i, utf8len(data[i]));
-    }
-
-    Str u8_slice(int start, int stop, int step) const{
-        std::stringstream ss;
-        if(is_ascii){
-            for(int i=start; step>0?i<stop:i>stop; i+=step) ss << data[i];
-        }else{
-            for(int i=start; step>0?i<stop:i>stop; i+=step) ss << u8_getitem(i);
-        }
-        return ss.str();
-    }
-
-    int u8_length() const {
-        return _byte_index_to_unicode(size);
-    }
+    int _unicode_index_to_byte(int i) const;
+    int _byte_index_to_unicode(int n) const;
+    Str u8_getitem(int i) const;
+    Str u8_slice(int start, int stop, int step) const;
+    int u8_length() const;
 };
 
 template<typename... Args>

+ 52 - 0
include/pocketpy/tuplelist.h

@@ -0,0 +1,52 @@
+#pragma once
+
+#include "common.h"
+#include "memory.h"
+#include "str.h"
+#include "vector.h"
+
+namespace pkpy {
+
+using List = pod_vector<PyObject*>;
+
+struct Tuple {
+    PyObject** _args;
+    PyObject* _inlined[3];
+    int _size;
+
+    Tuple(int n);
+    Tuple(std::initializer_list<PyObject*> list);
+    Tuple(const Tuple& other);
+    Tuple(Tuple&& other) noexcept;
+    Tuple(List&& other) noexcept;
+    ~Tuple();
+
+    bool is_inlined() const { return _args == _inlined; }
+    PyObject*& operator[](int i){ return _args[i]; }
+    PyObject* operator[](int i) const { return _args[i]; }
+
+    int size() const { return _size; }
+
+    PyObject** begin() const { return _args; }
+    PyObject** end() const { return _args + _size; }
+};
+
+// a lightweight view for function args, it does not own the memory
+struct ArgsView{
+    PyObject** _begin;
+    PyObject** _end;
+
+    ArgsView(PyObject** begin, PyObject** end) : _begin(begin), _end(end) {}
+    ArgsView(const Tuple& t) : _begin(t.begin()), _end(t.end()) {}
+
+    PyObject** begin() const { return _begin; }
+    PyObject** end() const { return _end; }
+    int size() const { return _end - _begin; }
+    bool empty() const { return _begin == _end; }
+    PyObject* operator[](int i) const { return _begin[i]; }
+
+    List to_list() const;
+    Tuple to_tuple() const;
+};
+
+}   // namespace pkpy

+ 0 - 0
src/vector.h → include/pocketpy/vector.h


+ 0 - 781
src/vm.h → include/pocketpy/vm.h

@@ -694,16 +694,6 @@ public:
     void _prepare_py_call(PyObject**, ArgsView, ArgsView, const FuncDecl_&);
 };
 
-inline void NativeFunc::check_size(VM* vm, ArgsView args) const{
-    if(args.size() != argc && argc != -1) {
-        vm->TypeError(fmt("expected ", argc, " arguments, got ", args.size()));
-    }
-}
-
-inline PyObject* NativeFunc::call(VM *vm, ArgsView args) const {
-    return f(vm, args);
-}
-
 DEF_NATIVE_2(Str, tp_str)
 DEF_NATIVE_2(List, tp_list)
 DEF_NATIVE_2(Tuple, tp_tuple)
@@ -742,7 +732,6 @@ PY_CAST_INT(unsigned int)
 PY_CAST_INT(unsigned long)
 PY_CAST_INT(unsigned long long)
 
-
 template<> inline float py_cast<float>(VM* vm, PyObject* obj){
     vm->check_float(obj);
     i64 bits = PK_BITS(obj) & Number::c1;
@@ -853,683 +842,6 @@ inline PyObject* py_var(VM* vm, PyObject* val){
     return val;
 }
 
-inline PyObject* VM::py_negate(PyObject* obj){
-    const PyTypeInfo* ti = _inst_type_info(obj);
-    if(ti->m__neg__) return ti->m__neg__(this, obj);
-    return call_method(obj, __neg__);
-}
-
-inline f64 VM::num_to_float(PyObject* obj){
-    if(is_float(obj)){
-        return _CAST(f64, obj);
-    } else if (is_int(obj)){
-        return (f64)_CAST(i64, obj);
-    }
-    TypeError("expected 'int' or 'float', got " + OBJ_NAME(_t(obj)).escape());
-    return 0;
-}
-
-inline bool VM::py_bool(PyObject* obj){
-    if(is_non_tagged_type(obj, tp_bool)) return obj == True;
-    if(obj == None) return false;
-    if(is_int(obj)) return _CAST(i64, obj) != 0;
-    if(is_float(obj)) return _CAST(f64, obj) != 0.0;
-    PyObject* self;
-    PyObject* len_f = get_unbound_method(obj, __len__, &self, false);
-    if(self != PY_NULL){
-        PyObject* ret = call_method(self, len_f);
-        return CAST(i64, ret) > 0;
-    }
-    return true;
-}
-
-inline PyObject* VM::py_list(PyObject* it){
-    auto _lock = heap.gc_scope_lock();
-    it = py_iter(it);
-    List list;
-    PyObject* obj = py_next(it);
-    while(obj != StopIteration){
-        list.push_back(obj);
-        obj = py_next(it);
-    }
-    return VAR(std::move(list));
-}
-
-inline void VM::parse_int_slice(const Slice& s, int length, int& start, int& stop, int& step){
-    auto clip = [](int value, int min, int max){
-        if(value < min) return min;
-        if(value > max) return max;
-        return value;
-    };
-    if(s.step == None) step = 1;
-    else step = CAST(int, s.step);
-    if(step == 0) ValueError("slice step cannot be zero");
-    if(step > 0){
-        if(s.start == None){
-            start = 0;
-        }else{
-            start = CAST(int, s.start);
-            if(start < 0) start += length;
-            start = clip(start, 0, length);
-        }
-        if(s.stop == None){
-            stop = length;
-        }else{
-            stop = CAST(int, s.stop);
-            if(stop < 0) stop += length;
-            stop = clip(stop, 0, length);
-        }
-    }else{
-        if(s.start == None){
-            start = length - 1;
-        }else{
-            start = CAST(int, s.start);
-            if(start < 0) start += length;
-            start = clip(start, -1, length - 1);
-        }
-        if(s.stop == None){
-            stop = -1;
-        }else{
-            stop = CAST(int, s.stop);
-            if(stop < 0) stop += length;
-            stop = clip(stop, -1, length - 1);
-        }
-    }
-}
-
-inline i64 VM::py_hash(PyObject* obj){
-    const PyTypeInfo* ti = _inst_type_info(obj);
-    if(ti->m__hash__) return ti->m__hash__(this, obj);
-    PyObject* ret = call_method(obj, __hash__);
-    return CAST(i64, ret);
-}
-
-inline PyObject* VM::format(Str spec, PyObject* obj){
-    if(spec.empty()) return py_str(obj);
-    char type;
-    switch(spec.end()[-1]){
-        case 'f': case 'd': case 's':
-            type = spec.end()[-1];
-            spec = spec.substr(0, spec.length() - 1);
-            break;
-        default: type = ' '; break;
-    }
-
-    char pad_c = ' ';
-    if(spec[0] == '0'){
-        pad_c = '0';
-        spec = spec.substr(1);
-    }
-    char align;
-    if(spec[0] == '>'){
-        align = '>';
-        spec = spec.substr(1);
-    }else if(spec[0] == '<'){
-        align = '<';
-        spec = spec.substr(1);
-    }else{
-        if(is_int(obj) || is_float(obj)) align = '>';
-        else align = '<';
-    }
-
-    int dot = spec.index(".");
-    int width, precision;
-    try{
-        if(dot >= 0){
-            width = Number::stoi(spec.substr(0, dot).str());
-            precision = Number::stoi(spec.substr(dot+1).str());
-        }else{
-            width = Number::stoi(spec.str());
-            precision = -1;
-        }
-    }catch(...){
-        ValueError("invalid format specifer");
-        UNREACHABLE();
-    }
-
-    if(type != 'f' && dot >= 0) ValueError("precision not allowed in the format specifier");
-    Str ret;
-    if(type == 'f'){
-        f64 val = num_to_float(obj);
-        if(precision < 0) precision = 6;
-        std::stringstream ss;
-        ss << std::fixed << std::setprecision(precision) << val;
-        ret = ss.str();
-    }else if(type == 'd'){
-        ret = std::to_string(CAST(i64, obj));
-    }else if(type == 's'){
-        ret = CAST(Str&, obj);
-    }else{
-        ret = CAST(Str&, py_str(obj));
-    }
-    if(width > ret.length()){
-        int pad = width - ret.length();
-        std::string padding(pad, pad_c);
-        if(align == '>') ret = padding.c_str() + ret;
-        else ret = ret + padding.c_str();
-    }
-    return VAR(ret);
-}
-
-inline PyObject* VM::new_module(StrName name) {
-    PyObject* obj = heap._new<DummyModule>(tp_module, DummyModule());
-    obj->attr().set("__name__", VAR(name.sv()));
-    // we do not allow override in order to avoid memory leak
-    // it is because Module objects are not garbage collected
-    if(_modules.contains(name)) throw std::runtime_error("module already exists");
-    _modules.set(name, obj);
-    return obj;
-}
-
-inline std::string _opcode_argstr(VM* vm, Bytecode byte, const CodeObject* co){
-    std::string argStr = byte.arg == -1 ? "" : std::to_string(byte.arg);
-    switch(byte.op){
-        case OP_LOAD_CONST:
-            if(vm != nullptr){
-                argStr += fmt(" (", CAST(Str, vm->py_repr(co->consts[byte.arg])), ")");
-            }
-            break;
-        case OP_LOAD_NAME: case OP_LOAD_GLOBAL: case OP_LOAD_NONLOCAL: case OP_STORE_GLOBAL:
-        case OP_LOAD_ATTR: case OP_LOAD_METHOD: case OP_STORE_ATTR: case OP_DELETE_ATTR:
-        case OP_IMPORT_NAME: case OP_BEGIN_CLASS: case OP_RAISE:
-        case OP_DELETE_GLOBAL: case OP_INC_GLOBAL: case OP_DEC_GLOBAL: case OP_STORE_CLASS_ATTR:
-            argStr += fmt(" (", StrName(byte.arg).sv(), ")");
-            break;
-        case OP_LOAD_FAST: case OP_STORE_FAST: case OP_DELETE_FAST: case OP_INC_FAST: case OP_DEC_FAST:
-            argStr += fmt(" (", co->varnames[byte.arg].sv(), ")");
-            break;
-        case OP_LOAD_FUNCTION:
-            argStr += fmt(" (", co->func_decls[byte.arg]->code->name, ")");
-            break;
-    }
-    return argStr;
-}
-
-inline Str VM::disassemble(CodeObject_ co){
-    auto pad = [](const Str& s, const int n){
-        if(s.length() >= n) return s.substr(0, n);
-        return s + std::string(n - s.length(), ' ');
-    };
-
-    std::vector<int> jumpTargets;
-    for(auto byte : co->codes){
-        if(byte.op == OP_JUMP_ABSOLUTE || byte.op == OP_POP_JUMP_IF_FALSE || byte.op == OP_SHORTCUT_IF_FALSE_OR_POP){
-            jumpTargets.push_back(byte.arg);
-        }
-    }
-    std::stringstream ss;
-    int prev_line = -1;
-    for(int i=0; i<co->codes.size(); i++){
-        const Bytecode& byte = co->codes[i];
-        Str line = std::to_string(co->lines[i]);
-        if(co->lines[i] == prev_line) line = "";
-        else{
-            if(prev_line != -1) ss << "\n";
-            prev_line = co->lines[i];
-        }
-
-        std::string pointer;
-        if(std::find(jumpTargets.begin(), jumpTargets.end(), i) != jumpTargets.end()){
-            pointer = "-> ";
-        }else{
-            pointer = "   ";
-        }
-        ss << pad(line, 8) << pointer << pad(std::to_string(i), 3);
-        ss << " " << pad(OP_NAMES[byte.op], 25) << " ";
-        // ss << pad(byte.arg == -1 ? "" : std::to_string(byte.arg), 5);
-        std::string argStr = _opcode_argstr(this, byte, co.get());
-        ss << argStr;
-        // ss << pad(argStr, 40);      // may overflow
-        // ss << co->blocks[byte.block].type;
-        if(i != co->codes.size() - 1) ss << '\n';
-    }
-
-    for(auto& decl: co->func_decls){
-        ss << "\n\n" << "Disassembly of " << decl->code->name << ":\n";
-        ss << disassemble(decl->code);
-    }
-    ss << "\n";
-    return Str(ss.str());
-}
-
-#if PK_DEBUG_CEVAL_STEP
-inline void VM::_log_s_data(const char* title) {
-    if(_main == nullptr) return;
-    if(callstack.empty()) return;
-    std::stringstream ss;
-    if(title) ss << title << " | ";
-    std::map<PyObject**, int> sp_bases;
-    for(Frame& f: callstack.data()){
-        if(f._sp_base == nullptr) FATAL_ERROR();
-        sp_bases[f._sp_base] += 1;
-    }
-    FrameId frame = top_frame();
-    int line = frame->co->lines[frame->_ip];
-    ss << frame->co->name << ":" << line << " [";
-    for(PyObject** p=s_data.begin(); p!=s_data.end(); p++){
-        ss << std::string(sp_bases[p], '|');
-        if(sp_bases[p] > 0) ss << " ";
-        PyObject* obj = *p;
-        if(obj == nullptr) ss << "(nil)";
-        else if(obj == PY_NULL) ss << "NULL";
-        else if(is_int(obj)) ss << CAST(i64, obj);
-        else if(is_float(obj)) ss << CAST(f64, obj);
-        else if(is_type(obj, tp_str)) ss << CAST(Str, obj).escape();
-        else if(obj == None) ss << "None";
-        else if(obj == True) ss << "True";
-        else if(obj == False) ss << "False";
-        else if(is_type(obj, tp_function)){
-            auto& f = CAST(Function&, obj);
-            ss << f.decl->code->name << "(...)";
-        } else if(is_type(obj, tp_type)){
-            Type t = PK_OBJ_GET(Type, obj);
-            ss << "<class " + _all_types[t].name.escape() + ">";
-        } else if(is_type(obj, tp_list)){
-            auto& t = CAST(List&, obj);
-            ss << "list(size=" << t.size() << ")";
-        } else if(is_type(obj, tp_tuple)){
-            auto& t = CAST(Tuple&, obj);
-            ss << "tuple(size=" << t.size() << ")";
-        } else ss << "(" << obj_type_name(this, obj->type) << ")";
-        ss << ", ";
-    }
-    std::string output = ss.str();
-    if(!s_data.empty()) {
-        output.pop_back(); output.pop_back();
-    }
-    output.push_back(']');
-    Bytecode byte = frame->co->codes[frame->_ip];
-    std::cout << output << " " << OP_NAMES[byte.op] << " " << _opcode_argstr(nullptr, byte, frame->co) << std::endl;
-}
-#endif
-
-inline void VM::init_builtin_types(){
-    _all_types.push_back({heap._new<Type>(Type(1), Type(0)), -1, "object", true});
-    _all_types.push_back({heap._new<Type>(Type(1), Type(1)), 0, "type", false});
-    tp_object = 0; tp_type = 1;
-
-    tp_int = _new_type_object("int");
-    tp_float = _new_type_object("float");
-    if(tp_int.index != kTpIntIndex || tp_float.index != kTpFloatIndex) FATAL_ERROR();
-
-    tp_bool = _new_type_object("bool");
-    tp_str = _new_type_object("str");
-    tp_list = _new_type_object("list");
-    tp_tuple = _new_type_object("tuple");
-    tp_slice = _new_type_object("slice");
-    tp_range = _new_type_object("range");
-    tp_module = _new_type_object("module");
-    tp_function = _new_type_object("function");
-    tp_native_func = _new_type_object("native_func");
-    tp_bound_method = _new_type_object("bound_method");
-    tp_super = _new_type_object("super");
-    tp_exception = _new_type_object("Exception");
-    tp_bytes = _new_type_object("bytes");
-    tp_mappingproxy = _new_type_object("mappingproxy");
-    tp_dict = _new_type_object("dict");
-    tp_property = _new_type_object("property");
-    tp_star_wrapper = _new_type_object("_star_wrapper");
-
-    this->None = heap._new<Dummy>(_new_type_object("NoneType"), {});
-    this->NotImplemented = heap._new<Dummy>(_new_type_object("NotImplementedType"), {});
-    this->Ellipsis = heap._new<Dummy>(_new_type_object("ellipsis"), {});
-    this->True = heap._new<Dummy>(tp_bool, {});
-    this->False = heap._new<Dummy>(tp_bool, {});
-    this->StopIteration = heap._new<Dummy>(_new_type_object("StopIterationType"), {});
-
-    this->builtins = new_module("builtins");
-    
-    // setup public types
-    builtins->attr().set("type", _t(tp_type));
-    builtins->attr().set("object", _t(tp_object));
-    builtins->attr().set("bool", _t(tp_bool));
-    builtins->attr().set("int", _t(tp_int));
-    builtins->attr().set("float", _t(tp_float));
-    builtins->attr().set("str", _t(tp_str));
-    builtins->attr().set("list", _t(tp_list));
-    builtins->attr().set("tuple", _t(tp_tuple));
-    builtins->attr().set("range", _t(tp_range));
-    builtins->attr().set("bytes", _t(tp_bytes));
-    builtins->attr().set("dict", _t(tp_dict));
-    builtins->attr().set("property", _t(tp_property));
-    builtins->attr().set("StopIteration", StopIteration);
-    builtins->attr().set("NotImplemented", NotImplemented);
-    builtins->attr().set("slice", _t(tp_slice));
-
-    post_init();
-    for(int i=0; i<_all_types.size(); i++){
-        _all_types[i].obj->attr()._try_perfect_rehash();
-    }
-    for(auto [k, v]: _modules.items()) v->attr()._try_perfect_rehash();
-    this->_main = new_module("__main__");
-}
-
-// `heap.gc_scope_lock();` needed before calling this function
-inline void VM::_unpack_as_list(ArgsView args, List& list){
-    for(PyObject* obj: args){
-        if(is_non_tagged_type(obj, tp_star_wrapper)){
-            const StarWrapper& w = _CAST(StarWrapper&, obj);
-            // maybe this check should be done in the compile time
-            if(w.level != 1) TypeError("expected level 1 star wrapper");
-            PyObject* _0 = py_iter(w.obj);
-            PyObject* _1 = py_next(_0);
-            while(_1 != StopIteration){
-                list.push_back(_1);
-                _1 = py_next(_0);
-            }
-        }else{
-            list.push_back(obj);
-        }
-    }
-}
-
-// `heap.gc_scope_lock();` needed before calling this function
-inline void VM::_unpack_as_dict(ArgsView args, Dict& dict){
-    for(PyObject* obj: args){
-        if(is_non_tagged_type(obj, tp_star_wrapper)){
-            const StarWrapper& w = _CAST(StarWrapper&, obj);
-            // maybe this check should be done in the compile time
-            if(w.level != 2) TypeError("expected level 2 star wrapper");
-            const Dict& other = CAST(Dict&, w.obj);
-            dict.update(other);
-        }else{
-            const Tuple& t = CAST(Tuple&, obj);
-            if(t.size() != 2) TypeError("expected tuple of length 2");
-            dict.set(t[0], t[1]);
-        }
-    }
-}
-
-inline void VM::_prepare_py_call(PyObject** buffer, ArgsView args, ArgsView kwargs, const FuncDecl_& decl){
-    const CodeObject* co = decl->code.get();
-    int co_nlocals = co->varnames.size();
-    int decl_argc = decl->args.size();
-
-    if(args.size() < decl_argc){
-        vm->TypeError(fmt(
-            "expected ", decl_argc, " positional arguments, got ", args.size(),
-            " (", co->name, ')'
-        ));
-    }
-
-    int i = 0;
-    // prepare args
-    for(int index: decl->args) buffer[index] = args[i++];
-    // set extra varnames to nullptr
-    for(int j=i; j<co_nlocals; j++) buffer[j] = PY_NULL;
-    // prepare kwdefaults
-    for(auto& kv: decl->kwargs) buffer[kv.key] = kv.value;
-    
-    // handle *args
-    if(decl->starred_arg != -1){
-        ArgsView vargs(args.begin() + i, args.end());
-        buffer[decl->starred_arg] = VAR(vargs.to_tuple());
-        i += vargs.size();
-    }else{
-        // kwdefaults override
-        for(auto& kv: decl->kwargs){
-            if(i >= args.size()) break;
-            buffer[kv.key] = args[i++];
-        }
-        if(i < args.size()) TypeError(fmt("too many arguments", " (", decl->code->name, ')'));
-    }
-    
-    PyObject* vkwargs;
-    if(decl->starred_kwarg != -1){
-        vkwargs = VAR(Dict(this));
-        buffer[decl->starred_kwarg] = vkwargs;
-    }else{
-        vkwargs = nullptr;
-    }
-
-    for(int j=0; j<kwargs.size(); j+=2){
-        StrName key(CAST(int, kwargs[j]));
-        int index = co->varnames_inv.try_get(key);
-        if(index < 0){
-            if(vkwargs == nullptr){
-                TypeError(fmt(key.escape(), " is an invalid keyword argument for ", co->name, "()"));
-            }else{
-                Dict& dict = _CAST(Dict&, vkwargs);
-                dict.set(VAR(key.sv()), kwargs[j+1]);
-            }
-        }else{
-            buffer[index] = kwargs[j+1];
-        }
-    }
-}
-
-inline PyObject* VM::vectorcall(int ARGC, int KWARGC, bool op_call){
-    PyObject** p1 = s_data._sp - KWARGC*2;
-    PyObject** p0 = p1 - ARGC - 2;
-    // [callable, <self>, args..., kwargs...]
-    //      ^p0                    ^p1      ^_sp
-    PyObject* callable = p1[-(ARGC + 2)];
-    bool method_call = p1[-(ARGC + 1)] != PY_NULL;
-
-    // handle boundmethod, do a patch
-    if(is_non_tagged_type(callable, tp_bound_method)){
-        if(method_call) FATAL_ERROR();
-        auto& bm = CAST(BoundMethod&, callable);
-        callable = bm.func;      // get unbound method
-        p1[-(ARGC + 2)] = bm.func;
-        p1[-(ARGC + 1)] = bm.self;
-        method_call = true;
-        // [unbound, self, args..., kwargs...]
-    }
-
-    ArgsView args(p1 - ARGC - int(method_call), p1);
-    ArgsView kwargs(p1, s_data._sp);
-
-    static THREAD_LOCAL PyObject* buffer[PK_MAX_CO_VARNAMES];
-
-    if(is_non_tagged_type(callable, tp_native_func)){
-        const auto& f = PK_OBJ_GET(NativeFunc, callable);
-        PyObject* ret;
-        if(f.decl != nullptr){
-            int co_nlocals = f.decl->code->varnames.size();
-            _prepare_py_call(buffer, args, kwargs, f.decl);
-            // copy buffer back to stack
-            s_data.reset(args.begin());
-            for(int j=0; j<co_nlocals; j++) PUSH(buffer[j]);
-            ret = f.call(vm, ArgsView(s_data._sp - co_nlocals, s_data._sp));
-        }else{
-            if(KWARGC != 0) TypeError("old-style native_func does not accept keyword arguments");
-            f.check_size(this, args);
-            ret = f.call(this, args);
-        }
-        s_data.reset(p0);
-        return ret;
-    }
-
-    if(is_non_tagged_type(callable, tp_function)){
-        /*****************_py_call*****************/
-        // callable must be a `function` object
-        if(s_data.is_overflow()) StackOverflowError();
-
-        const Function& fn = PK_OBJ_GET(Function, callable);
-        const FuncDecl_& decl = fn.decl;
-        const CodeObject* co = decl->code.get();
-        int co_nlocals = co->varnames.size();
-
-        _prepare_py_call(buffer, args, kwargs, decl);
-        
-        if(co->is_generator){
-            s_data.reset(p0);
-            return _py_generator(
-                Frame(&s_data, nullptr, co, fn._module, callable),
-                ArgsView(buffer, buffer + co_nlocals)
-            );
-        }
-
-        // copy buffer back to stack
-        s_data.reset(args.begin());
-        for(int j=0; j<co_nlocals; j++) PUSH(buffer[j]);
-        callstack.emplace(&s_data, p0, co, fn._module, callable, FastLocals(co, args.begin()));
-        if(op_call) return PY_OP_CALL;
-        return _run_top_frame();
-        /*****************_py_call*****************/
-    }
-
-    if(is_non_tagged_type(callable, tp_type)){
-        if(method_call) FATAL_ERROR();
-        // [type, NULL, args..., kwargs...]
-
-        DEF_SNAME(__new__);
-        PyObject* new_f = find_name_in_mro(callable, __new__);
-        PyObject* obj;
-#if PK_DEBUG_EXTRA_CHECK
-        PK_ASSERT(new_f != nullptr);
-#endif
-        if(new_f == cached_object__new__) {
-            // fast path for object.__new__
-            Type t = PK_OBJ_GET(Type, callable);
-            obj= vm->heap.gcnew<DummyInstance>(t, {});
-        }else{
-            PUSH(new_f);
-            PUSH(PY_NULL);
-            PUSH(callable);    // cls
-            for(PyObject* o: args) PUSH(o);
-            for(PyObject* o: kwargs) PUSH(o);
-            // if obj is not an instance of callable, the behavior is undefined
-            obj = vectorcall(ARGC+1, KWARGC);
-        }
-
-        // __init__
-        PyObject* self;
-        DEF_SNAME(__init__);
-        callable = get_unbound_method(obj, __init__, &self, false);
-        if (self != PY_NULL) {
-            // replace `NULL` with `self`
-            p1[-(ARGC + 2)] = callable;
-            p1[-(ARGC + 1)] = self;
-            // [init_f, self, args..., kwargs...]
-            vectorcall(ARGC, KWARGC);
-            // We just discard the return value of `__init__`
-            // in cpython it raises a TypeError if the return value is not None
-        }else{
-            // manually reset the stack
-            s_data.reset(p0);
-        }
-        return obj;
-    }
-
-    // handle `__call__` overload
-    PyObject* self;
-    DEF_SNAME(__call__);
-    PyObject* call_f = get_unbound_method(callable, __call__, &self, false);
-    if(self != PY_NULL){
-        p1[-(ARGC + 2)] = call_f;
-        p1[-(ARGC + 1)] = self;
-        // [call_f, self, args..., kwargs...]
-        return vectorcall(ARGC, KWARGC, false);
-    }
-    TypeError(OBJ_NAME(_t(callable)).escape() + " object is not callable");
-    return nullptr;
-}
-
-// https://docs.python.org/3/howto/descriptor.html#invocation-from-an-instance
-inline PyObject* VM::getattr(PyObject* obj, StrName name, bool throw_err){
-    PyObject* objtype;
-    // handle super() proxy
-    if(is_non_tagged_type(obj, tp_super)){
-        const Super& super = PK_OBJ_GET(Super, obj);
-        obj = super.first;
-        objtype = _t(super.second);
-    }else{
-        objtype = _t(obj);
-    }
-    PyObject* cls_var = find_name_in_mro(objtype, name);
-    if(cls_var != nullptr){
-        // handle descriptor
-        if(is_non_tagged_type(cls_var, tp_property)){
-            const Property& prop = _CAST(Property&, cls_var);
-            return call(prop.getter, obj);
-        }
-    }
-    // handle instance __dict__
-    if(!is_tagged(obj) && obj->is_attr_valid()){
-        PyObject* val = obj->attr().try_get(name);
-        if(val != nullptr) return val;
-    }
-    if(cls_var != nullptr){
-        // bound method is non-data descriptor
-        if(is_non_tagged_type(cls_var, tp_function) || is_non_tagged_type(cls_var, tp_native_func)){
-            return VAR(BoundMethod(obj, cls_var));
-        }
-        return cls_var;
-    }
-    if(throw_err) AttributeError(obj, name);
-    return nullptr;
-}
-
-// used by OP_LOAD_METHOD
-// try to load a unbound method (fallback to `getattr` if not found)
-inline PyObject* VM::get_unbound_method(PyObject* obj, StrName name, PyObject** self, bool throw_err, bool fallback){
-    *self = PY_NULL;
-    PyObject* objtype;
-    // handle super() proxy
-    if(is_non_tagged_type(obj, tp_super)){
-        const Super& super = PK_OBJ_GET(Super, obj);
-        obj = super.first;
-        objtype = _t(super.second);
-    }else{
-        objtype = _t(obj);
-    }
-    PyObject* cls_var = find_name_in_mro(objtype, name);
-
-    if(fallback){
-        if(cls_var != nullptr){
-            // handle descriptor
-            if(is_non_tagged_type(cls_var, tp_property)){
-                const Property& prop = _CAST(Property&, cls_var);
-                return call(prop.getter, obj);
-            }
-        }
-        // handle instance __dict__
-        if(!is_tagged(obj) && obj->is_attr_valid()){
-            PyObject* val = obj->attr().try_get(name);
-            if(val != nullptr) return val;
-        }
-    }
-
-    if(cls_var != nullptr){
-        if(is_non_tagged_type(cls_var, tp_function) || is_non_tagged_type(cls_var, tp_native_func)){
-            *self = obj;
-        }
-        return cls_var;
-    }
-    if(throw_err) AttributeError(obj, name);
-    return nullptr;
-}
-
-inline void VM::setattr(PyObject* obj, StrName name, PyObject* value){
-    PyObject* objtype;
-    // handle super() proxy
-    if(is_non_tagged_type(obj, tp_super)){
-        Super& super = PK_OBJ_GET(Super, obj);
-        obj = super.first;
-        objtype = _t(super.second);
-    }else{
-        objtype = _t(obj);
-    }
-    PyObject* cls_var = find_name_in_mro(objtype, name);
-    if(cls_var != nullptr){
-        // handle descriptor
-        if(is_non_tagged_type(cls_var, tp_property)){
-            const Property& prop = _CAST(Property&, cls_var);
-            if(prop.setter != vm->None){
-                call(prop.setter, obj, value);
-            }else{
-                TypeError(fmt("readonly attribute: ", name.escape()));
-            }
-            return;
-        }
-    }
-    // handle instance __dict__
-    if(is_tagged(obj) || !obj->is_attr_valid()) TypeError("cannot set attribute");
-    obj->attr().set(name, value);
-}
-
 template<int ARGC>
 PyObject* VM::bind_method(PyObject* obj, Str name, NativeFuncC fn) {
     check_non_tagged_type(obj, tp_type);
@@ -1545,55 +857,6 @@ PyObject* VM::bind_func(PyObject* obj, Str name, NativeFuncC fn) {
     return nf;
 }
 
-inline PyObject* VM::bind(PyObject* obj, const char* sig, NativeFuncC fn){
-    return bind(obj, sig, nullptr, fn);
-}
-
-inline PyObject* VM::bind(PyObject* obj, const char* sig, const char* docstring, NativeFuncC fn){
-    CodeObject_ co;
-    try{
-        // fn(a, b, *c, d=1) -> None
-        co = compile("def " + Str(sig) + " : pass", "<bind>", EXEC_MODE);
-    }catch(Exception& e){
-        throw std::runtime_error("invalid signature: " + std::string(sig));
-    }
-    if(co->func_decls.size() != 1){
-        throw std::runtime_error("expected 1 function declaration");
-    }
-    FuncDecl_ decl = co->func_decls[0];
-    decl->signature = Str(sig);
-    if(docstring != nullptr){
-        decl->docstring = Str(docstring).strip();
-    }
-    PyObject* f_obj = VAR(NativeFunc(fn, decl));
-    obj->attr().set(decl->code->name, f_obj);
-    return f_obj;
-}
-
-inline void VM::_error(Exception e){
-    if(callstack.empty()){
-        e.is_re = false;
-        throw e;
-    }
-    PUSH(VAR(e));
-    _raise();
-}
-
-inline void ManagedHeap::mark() {
-    for(PyObject* obj: _no_gc) PK_OBJ_MARK(obj);
-    for(auto& frame : vm->callstack.data()) frame._gc_mark();
-    for(PyObject* obj: vm->s_data) PK_OBJ_MARK(obj);
-    if(_gc_marker_ex) _gc_marker_ex(vm);
-    if(vm->_last_exception) PK_OBJ_MARK(vm->_last_exception);
-}
-
-inline Str obj_type_name(VM *vm, Type type){
-    return vm->_all_types[type].name;
-}
-
-#undef PY_VAR_INT
-#undef PY_VAR_FLOAT
-
 /***************************************************/
 
 template<typename T>
@@ -1614,48 +877,4 @@ PyObject* PyArrayGetItem(VM* vm, PyObject* obj, PyObject* index){
     i = vm->normalized_index(i, self.size());
     return self[i];
 }
-
-inline void VM::bind__hash__(Type type, i64 (*f)(VM*, PyObject*)){
-    PyObject* obj = _t(type);
-    _all_types[type].m__hash__ = f;
-    PyObject* nf = bind_method<0>(obj, "__hash__", [](VM* vm, ArgsView args){
-        i64 ret = lambda_get_userdata<i64(*)(VM*, PyObject*)>(args.begin())(vm, args[0]);
-        return VAR(ret);
-    });
-    PK_OBJ_GET(NativeFunc, nf).set_userdata(f);
-}
-
-inline void VM::bind__len__(Type type, i64 (*f)(VM*, PyObject*)){
-    PyObject* obj = _t(type);
-    _all_types[type].m__len__ = f;
-    PyObject* nf = bind_method<0>(obj, "__len__", [](VM* vm, ArgsView args){
-        i64 ret = lambda_get_userdata<i64(*)(VM*, PyObject*)>(args.begin())(vm, args[0]);
-        return VAR(ret);
-    });
-    PK_OBJ_GET(NativeFunc, nf).set_userdata(f);
-}
-
-
-inline void Dict::_probe(PyObject *key, bool &ok, int &i) const{
-    ok = false;
-    i = vm->py_hash(key) & _mask;
-    while(_items[i].first != nullptr) {
-        if(vm->py_equals(_items[i].first, key)) { ok = true; break; }
-        // https://github.com/python/cpython/blob/3.8/Objects/dictobject.c#L166
-        i = ((5*i) + 1) & _mask;
-    }
-}
-
-inline void CodeObjectSerializer::write_object(VM *vm, PyObject *obj){
-    if(is_int(obj)) write_int(_CAST(i64, obj));
-    else if(is_float(obj)) write_float(_CAST(f64, obj));
-    else if(is_type(obj, vm->tp_str)) write_str(_CAST(Str&, obj));
-    else if(is_type(obj, vm->tp_bool)) write_bool(_CAST(bool, obj));
-    else if(obj == vm->None) write_none();
-    else if(obj == vm->Ellipsis) write_ellipsis();
-    else{
-        throw std::runtime_error(fmt(OBJ_NAME(vm->_t(obj)).escape(), " is not serializable"));
-    }
-}
-
 }   // namespace pkpy

+ 1 - 1
preprocess.py

@@ -37,5 +37,5 @@ namespace pkpy{
 '''
     return header
 
-with open("src/_generated.h", "w", encoding='utf-8') as f:
+with open("include/pocketpy/_generated.h", "w", encoding='utf-8') as f:
     f.write(generate_python_sources())

+ 6 - 3
scripts/loc.py

@@ -11,13 +11,16 @@ def get_loc_for_dir(path):
     loc_ex = 0
     for root, dirs, files in os.walk(path):
         for file in files:
-            if file.endswith('.h'):
+            if file.endswith('.h') or file.endswith('.cpp'):
                 _i = get_loc(os.path.join(root, file))
                 print(f"{file}: {_i}")
                 if file.startswith('_'):
                     loc_ex += _i
                 else:
                     loc += _i
-    return f'{loc} (+{loc_ex})'
+    return f'{path}: {loc} (+{loc_ex})'
 
-print(get_loc_for_dir('src'))
+
+print(get_loc_for_dir('include/pocketpy'))
+print()
+print(get_loc_for_dir('src'))

+ 6 - 19
src/base64.h → src/base64.cpp

@@ -1,12 +1,6 @@
-#pragma once
+#include "pocketpy/base64.h"
 
-#include "common.h"
-
-#if PK_MODULE_BASE64
-
-#include "cffi.h"
-
-namespace pkpy {
+namespace pkpy{
 
 // https://github.com/zhicheng/base64/blob/master/base64.c
 
@@ -77,7 +71,7 @@ const unsigned char base64de[] = {
 	    49,  50,  51, 255, 255, 255, 255, 255
 };
 
-inline static unsigned int
+static unsigned int
 base64_encode(const unsigned char *in, unsigned int inlen, char *out)
 {
 	int s;
@@ -126,7 +120,7 @@ base64_encode(const unsigned char *in, unsigned int inlen, char *out)
 	return j;
 }
 
-inline static unsigned int
+static unsigned int
 base64_decode(const char *in, unsigned int inlen, unsigned char *out)
 {
 	unsigned int i;
@@ -171,7 +165,7 @@ base64_decode(const char *in, unsigned int inlen, unsigned char *out)
 	return j;
 }
 
-inline void add_module_base64(VM* vm){
+void add_module_base64(VM* vm){
     PyObject* mod = vm->new_module("base64");
 
     // b64encode
@@ -193,11 +187,4 @@ inline void add_module_base64(VM* vm){
     });
 }
 
-} // namespace pkpy
-
-
-#else
-
-ADD_MODULE_PLACEHOLDER(base64)
-
-#endif
+}	// namespace pkpy

+ 5 - 7
src/ceval.h → src/ceval.cpp

@@ -1,12 +1,10 @@
-#pragma once
-
-#include "common.h"
-#include "namedict.h"
-#include "vm.h"
+#include "pocketpy/common.h"
+#include "pocketpy/namedict.h"
+#include "pocketpy/vm.h"
 
 namespace pkpy{
 
-inline PyObject* VM::_run_top_frame(){
+PyObject* VM::_run_top_frame(){
     DEF_SNAME(add);
     DEF_SNAME(set);
     DEF_SNAME(__enter__);
@@ -51,7 +49,7 @@ __NEXT_FRAME:
 #if PK_ENABLE_COMPUTED_GOTO
 static void* OP_LABELS[] = {
     #define OPCODE(name) &&CASE_OP_##name,
-    #include "opcodes.h"
+    #include "pocketpy/opcodes.h"
     #undef OPCODE
 };
 

+ 3 - 15
src/easing.h → src/easing.cpp

@@ -1,10 +1,4 @@
-#pragma once
-
-#include "common.h"
-
-#if PK_MODULE_EASING
-
-#include "cffi.h"
+#include "pocketpy/easing.h"
 
 namespace pkpy{
 
@@ -212,7 +206,7 @@ static double easeInOutBounce( double x ) {
     : (1 + easeOutBounce(2 * x - 1)) / 2;
 }
 
-inline void add_module_easing(VM* vm){
+void add_module_easing(VM* vm){
     PyObject* mod = vm->new_module("easing");
 
 #define EASE(name)  \
@@ -256,10 +250,4 @@ inline void add_module_easing(VM* vm){
 #undef EASE
 }
 
-} // namespace pkpy
-
-#else
-
-ADD_MODULE_PLACEHOLDER(easing)
-
-#endif
+}   // namespace pkpy

+ 16 - 55
src/error.h → src/error.cpp

@@ -1,45 +1,8 @@
-#pragma once
-
-#include "namedict.h"
-#include "str.h"
-#include "tuplelist.h"
+#include "pocketpy/error.h"
 
 namespace pkpy{
 
-struct NeedMoreLines {
-    NeedMoreLines(bool is_compiling_class) : is_compiling_class(is_compiling_class) {}
-    bool is_compiling_class;
-};
-
-struct HandledException {};
-struct UnhandledException {};
-struct ToBeRaisedException {};
-
-enum CompileMode {
-    EXEC_MODE,
-    EVAL_MODE,
-    REPL_MODE,
-    JSON_MODE,
-    CELL_MODE
-};
-
-struct SourceData {
-    std::string source;
-    Str filename;
-    std::vector<const char*> line_starts;
-    CompileMode mode;
-
-    std::pair<const char*,const char*> get_line(int lineno) const {
-        if(lineno == -1) return {nullptr, nullptr};
-        lineno -= 1;
-        if(lineno < 0) lineno = 0;
-        const char* _start = line_starts.at(lineno);
-        const char* i = _start;
-        while(*i != '\n' && *i != '\0') i++;
-        return {_start, i};
-    }
-
-    SourceData(const Str& source, const Str& filename, CompileMode mode) {
+    SourceData::SourceData(const Str& source, const Str& filename, CompileMode mode) {
         int index = 0;
         // Skip utf8 BOM if there is any.
         if (strncmp(source.begin(), "\xEF\xBB\xBF", 3) == 0) index += 3;
@@ -57,7 +20,17 @@ struct SourceData {
         this->mode = mode;
     }
 
-    Str snapshot(int lineno, const char* cursor=nullptr){
+    std::pair<const char*,const char*> SourceData::get_line(int lineno) const {
+        if(lineno == -1) return {nullptr, nullptr};
+        lineno -= 1;
+        if(lineno < 0) lineno = 0;
+        const char* _start = line_starts.at(lineno);
+        const char* i = _start;
+        while(*i != '\n' && *i != '\0') i++;
+        return {_start, i};
+    }
+
+    Str SourceData::snapshot(int lineno, const char* cursor){
         std::stringstream ss;
         ss << "  " << "File \"" << filename << "\", line " << lineno << '\n';
         std::pair<const char*,const char*> pair = get_line(lineno);
@@ -75,25 +48,14 @@ struct SourceData {
         }
         return ss.str();
     }
-};
-
-class Exception {
-    using StackTrace = stack<Str>;
-    StrName type;
-    Str msg;
-    StackTrace stacktrace;
-public:
-    Exception(StrName type, Str msg): type(type), msg(msg) {}
-    bool match_type(StrName t) const { return this->type == t;}
-    bool is_re = true;
 
-    void st_push(Str snapshot){
+    void Exception::st_push(Str snapshot){
         if(stacktrace.size() >= 8) return;
         stacktrace.push(snapshot);
     }
 
-    Str summary() const {
-        StackTrace st(stacktrace);
+    Str Exception::summary() const {
+        stack<Str> st(stacktrace);
         std::stringstream ss;
         if(is_re) ss << "Traceback (most recent call last):\n";
         while(!st.empty()) { ss << st.top() << '\n'; st.pop(); }
@@ -101,6 +63,5 @@ public:
         else ss << type.sv();
         return ss.str();
     }
-};
 
 }   // namespace pkpy

+ 8 - 59
src/iter.h → src/iter.cpp

@@ -1,18 +1,8 @@
-#pragma once
-
-#include "cffi.h"
-#include "common.h"
-#include "frame.h"
+#include "pocketpy/iter.h"
 
 namespace pkpy{
 
-struct RangeIter{
-    PY_CLASS(RangeIter, builtins, "_range_iterator")
-    Range r;
-    i64 current;
-    RangeIter(Range r) : r(r), current(r.start) {}
-
-    static void _register(VM* vm, PyObject* mod, PyObject* type){
+    void RangeIter::_register(VM* vm, PyObject* mod, PyObject* type){
         vm->_all_types[PK_OBJ_GET(Type, type)].subclass_enabled = false;
         vm->bind_notimplemented_constructor<RangeIter>(type);
         vm->bind__iter__(PK_OBJ_GET(Type, type), [](VM* vm, PyObject* obj){ return obj; });
@@ -24,21 +14,8 @@ struct RangeIter{
             return VAR(self.current - self.r.step);
         });
     }
-};
-
-struct ArrayIter{
-    PY_CLASS(ArrayIter, builtins, "_array_iterator")
-    PyObject* ref;
-    PyObject** begin;
-    PyObject** end;
-    PyObject** current;
-
-    ArrayIter(PyObject* ref, PyObject** begin, PyObject** end)
-        : ref(ref), begin(begin), end(end), current(begin) {}
 
-    void _gc_mark() const{ PK_OBJ_MARK(ref); }
-
-    static void _register(VM* vm, PyObject* mod, PyObject* type){
+    void ArrayIter::_register(VM* vm, PyObject* mod, PyObject* type){
         vm->_all_types[PK_OBJ_GET(Type, type)].subclass_enabled = false;
         vm->bind_notimplemented_constructor<ArrayIter>(type);
         vm->bind__iter__(PK_OBJ_GET(Type, type), [](VM* vm, PyObject* obj){ return obj; });
@@ -48,19 +25,8 @@ struct ArrayIter{
             return *self.current++;
         });
     }
-};
-
-struct StringIter{
-    PY_CLASS(StringIter, builtins, "_string_iterator")
-    PyObject* ref;
-    Str* str;
-    int index;
-
-    StringIter(PyObject* ref) : ref(ref), str(&PK_OBJ_GET(Str, ref)), index(0) {}
 
-    void _gc_mark() const{ PK_OBJ_MARK(ref); }
-
-    static void _register(VM* vm, PyObject* mod, PyObject* type){
+    void StringIter::_register(VM* vm, PyObject* mod, PyObject* type){
         vm->_all_types[PK_OBJ_GET(Type, type)].subclass_enabled = false;
         vm->bind_notimplemented_constructor<StringIter>(type);
         vm->bind__iter__(PK_OBJ_GET(Type, type), [](VM* vm, PyObject* obj){ return obj; });
@@ -71,24 +37,8 @@ struct StringIter{
             return VAR(self.str->u8_getitem(self.index++));
         });
     }
-};
-
-struct Generator{
-    PY_CLASS(Generator, builtins, "_generator")
-    Frame frame;
-    int state;      // 0,1,2
-    List s_backup;
-
-    Generator(Frame&& frame, ArgsView buffer): frame(std::move(frame)), state(0) {
-        for(PyObject* obj: buffer) s_backup.push_back(obj);
-    }
-
-    void _gc_mark() const{
-        frame._gc_mark();
-        for(PyObject* obj: s_backup) PK_OBJ_MARK(obj);
-    }
 
-    PyObject* next(VM* vm){
+    PyObject* Generator::next(VM* vm){
         if(state == 2) return vm->StopIteration;
         // reset frame._sp_base
         frame._sp_base = frame._s->_sp;
@@ -113,7 +63,7 @@ struct Generator{
         }
     }
 
-    static void _register(VM* vm, PyObject* mod, PyObject* type){
+    void Generator::_register(VM* vm, PyObject* mod, PyObject* type){
         vm->_all_types[PK_OBJ_GET(Type, type)].subclass_enabled = false;
         vm->bind_notimplemented_constructor<Generator>(type);
         vm->bind__iter__(PK_OBJ_GET(Type, type), [](VM* vm, PyObject* obj){ return obj; });
@@ -122,10 +72,9 @@ struct Generator{
             return self.next(vm);
         });
     }
-};
 
-inline PyObject* VM::_py_generator(Frame&& frame, ArgsView buffer){
+PyObject* VM::_py_generator(Frame&& frame, ArgsView buffer){
     return VAR_T(Generator, std::move(frame), buffer);
 }
 
-} // namespace pkpy
+}   // namespace pkpy

+ 12 - 377
src/linalg.h → src/linalg.cpp

@@ -1,310 +1,7 @@
-#pragma once
-
-#include "common.h"
-
-#if PK_MODULE_LINALG
-
-#include "cffi.h"
+#include "pocketpy/pocketpy.h"
 
 namespace pkpy{
 
-static constexpr float kEpsilon = 1e-4f;
-inline static bool isclose(float a, float b){ return fabsf(a - b) < kEpsilon; }
-
-struct Vec2{
-    float x, y;
-    Vec2() : x(0.0f), y(0.0f) {}
-    Vec2(float x, float y) : x(x), y(y) {}
-    Vec2(const Vec2& v) : x(v.x), y(v.y) {}
-
-    Vec2 operator+(const Vec2& v) const { return Vec2(x + v.x, y + v.y); }
-    Vec2& operator+=(const Vec2& v) { x += v.x; y += v.y; return *this; }
-    Vec2 operator-(const Vec2& v) const { return Vec2(x - v.x, y - v.y); }
-    Vec2& operator-=(const Vec2& v) { x -= v.x; y -= v.y; return *this; }
-    Vec2 operator*(float s) const { return Vec2(x * s, y * s); }
-    Vec2& operator*=(float s) { x *= s; y *= s; return *this; }
-    Vec2 operator/(float s) const { return Vec2(x / s, y / s); }
-    Vec2& operator/=(float s) { x /= s; y /= s; return *this; }
-    Vec2 operator-() const { return Vec2(-x, -y); }
-    bool operator==(const Vec2& v) const { return isclose(x, v.x) && isclose(y, v.y); }
-    bool operator!=(const Vec2& v) const { return !isclose(x, v.x) || !isclose(y, v.y); }
-    float dot(const Vec2& v) const { return x * v.x + y * v.y; }
-    float cross(const Vec2& v) const { return x * v.y - y * v.x; }
-    float length() const { return sqrtf(x * x + y * y); }
-    float length_squared() const { return x * x + y * y; }
-    Vec2 normalize() const { float l = length(); return Vec2(x / l, y / l); }
-};
-
-struct Vec3{
-    float x, y, z;
-    Vec3() : x(0.0f), y(0.0f), z(0.0f) {}
-    Vec3(float x, float y, float z) : x(x), y(y), z(z) {}
-    Vec3(const Vec3& v) : x(v.x), y(v.y), z(v.z) {}
-
-    Vec3 operator+(const Vec3& v) const { return Vec3(x + v.x, y + v.y, z + v.z); }
-    Vec3& operator+=(const Vec3& v) { x += v.x; y += v.y; z += v.z; return *this; }
-    Vec3 operator-(const Vec3& v) const { return Vec3(x - v.x, y - v.y, z - v.z); }
-    Vec3& operator-=(const Vec3& v) { x -= v.x; y -= v.y; z -= v.z; return *this; }
-    Vec3 operator*(float s) const { return Vec3(x * s, y * s, z * s); }
-    Vec3& operator*=(float s) { x *= s; y *= s; z *= s; return *this; }
-    Vec3 operator/(float s) const { return Vec3(x / s, y / s, z / s); }
-    Vec3& operator/=(float s) { x /= s; y /= s; z /= s; return *this; }
-    Vec3 operator-() const { return Vec3(-x, -y, -z); }
-    bool operator==(const Vec3& v) const { return isclose(x, v.x) && isclose(y, v.y) && isclose(z, v.z); }
-    bool operator!=(const Vec3& v) const { return !isclose(x, v.x) || !isclose(y, v.y) || !isclose(z, v.z); }
-    float dot(const Vec3& v) const { return x * v.x + y * v.y + z * v.z; }
-    Vec3 cross(const Vec3& v) const { return Vec3(y * v.z - z * v.y, z * v.x - x * v.z, x * v.y - y * v.x); }
-    float length() const { return sqrtf(x * x + y * y + z * z); }
-    float length_squared() const { return x * x + y * y + z * z; }
-    Vec3 normalize() const { float l = length(); return Vec3(x / l, y / l, z / l); }
-};
-
-struct Vec4{
-    float x, y, z, w;
-    Vec4() : x(0.0f), y(0.0f), z(0.0f), w(0.0f) {}
-    Vec4(float x, float y, float z, float w) : x(x), y(y), z(z), w(w) {}
-    Vec4(const Vec4& v) : x(v.x), y(v.y), z(v.z), w(v.w) {}
-
-    Vec4 operator+(const Vec4& v) const { return Vec4(x + v.x, y + v.y, z + v.z, w + v.w); }
-    Vec4& operator+=(const Vec4& v) { x += v.x; y += v.y; z += v.z; w += v.w; return *this; }
-    Vec4 operator-(const Vec4& v) const { return Vec4(x - v.x, y - v.y, z - v.z, w - v.w); }
-    Vec4& operator-=(const Vec4& v) { x -= v.x; y -= v.y; z -= v.z; w -= v.w; return *this; }
-    Vec4 operator*(float s) const { return Vec4(x * s, y * s, z * s, w * s); }
-    Vec4& operator*=(float s) { x *= s; y *= s; z *= s; w *= s; return *this; }
-    Vec4 operator/(float s) const { return Vec4(x / s, y / s, z / s, w / s); }
-    Vec4& operator/=(float s) { x /= s; y /= s; z /= s; w /= s; return *this; }
-    Vec4 operator-() const { return Vec4(-x, -y, -z, -w); }
-    bool operator==(const Vec4& v) const { return isclose(x, v.x) && isclose(y, v.y) && isclose(z, v.z) && isclose(w, v.w); }
-    bool operator!=(const Vec4& v) const { return !isclose(x, v.x) || !isclose(y, v.y) || !isclose(z, v.z) || !isclose(w, v.w); }
-    float dot(const Vec4& v) const { return x * v.x + y * v.y + z * v.z + w * v.w; }
-    float length() const { return sqrtf(x * x + y * y + z * z + w * w); }
-    float length_squared() const { return x * x + y * y + z * z + w * w; }
-    Vec4 normalize() const { float l = length(); return Vec4(x / l, y / l, z / l, w / l); }
-};
-
-struct Mat3x3{    
-    union {
-        struct {
-            float        _11, _12, _13;
-            float        _21, _22, _23;
-            float        _31, _32, _33;
-        };
-        float m[3][3];
-        float v[9];
-    };
-
-    Mat3x3() {}
-    Mat3x3(float _11, float _12, float _13,
-           float _21, float _22, float _23,
-           float _31, float _32, float _33)
-        : _11(_11), _12(_12), _13(_13)
-        , _21(_21), _22(_22), _23(_23)
-        , _31(_31), _32(_32), _33(_33) {}
-
-    void set_zeros(){ for (int i=0; i<9; ++i) v[i] = 0.0f; }
-    void set_ones(){ for (int i=0; i<9; ++i) v[i] = 1.0f; }
-    void set_identity(){ set_zeros(); _11 = _22 = _33 = 1.0f; }
-
-    static Mat3x3 zeros(){
-        static Mat3x3 ret(0, 0, 0, 0, 0, 0, 0, 0, 0);
-        return ret;
-    }
-
-    static Mat3x3 ones(){
-        static Mat3x3 ret(1, 1, 1, 1, 1, 1, 1, 1, 1);
-        return ret;
-    }
-
-    static Mat3x3 identity(){
-        static Mat3x3 ret(1, 0, 0, 0, 1, 0, 0, 0, 1);
-        return ret;
-    }
-
-    Mat3x3 operator+(const Mat3x3& other) const{ 
-        Mat3x3 ret;
-        for (int i=0; i<9; ++i) ret.v[i] = v[i] + other.v[i];
-        return ret;
-    }
-
-    Mat3x3 operator-(const Mat3x3& other) const{ 
-        Mat3x3 ret;
-        for (int i=0; i<9; ++i) ret.v[i] = v[i] - other.v[i];
-        return ret;
-    }
-
-    Mat3x3 operator*(float scalar) const{ 
-        Mat3x3 ret;
-        for (int i=0; i<9; ++i) ret.v[i] = v[i] * scalar;
-        return ret;
-    }
-
-    Mat3x3 operator/(float scalar) const{ 
-        Mat3x3 ret;
-        for (int i=0; i<9; ++i) ret.v[i] = v[i] / scalar;
-        return ret;
-    }
-
-    Mat3x3& operator+=(const Mat3x3& other){ 
-        for (int i=0; i<9; ++i) v[i] += other.v[i];
-        return *this;
-    }
-
-    Mat3x3& operator-=(const Mat3x3& other){ 
-        for (int i=0; i<9; ++i) v[i] -= other.v[i];
-        return *this;
-    }
-
-    Mat3x3& operator*=(float scalar){ 
-        for (int i=0; i<9; ++i) v[i] *= scalar;
-        return *this;
-    }
-
-    Mat3x3& operator/=(float scalar){ 
-        for (int i=0; i<9; ++i) v[i] /= scalar;
-        return *this;
-    }
-
-    Mat3x3 matmul(const Mat3x3& other) const{
-        Mat3x3 ret;
-        ret._11 = _11 * other._11 + _12 * other._21 + _13 * other._31;
-        ret._12 = _11 * other._12 + _12 * other._22 + _13 * other._32;
-        ret._13 = _11 * other._13 + _12 * other._23 + _13 * other._33;
-        ret._21 = _21 * other._11 + _22 * other._21 + _23 * other._31;
-        ret._22 = _21 * other._12 + _22 * other._22 + _23 * other._32;
-        ret._23 = _21 * other._13 + _22 * other._23 + _23 * other._33;
-        ret._31 = _31 * other._11 + _32 * other._21 + _33 * other._31;
-        ret._32 = _31 * other._12 + _32 * other._22 + _33 * other._32;
-        ret._33 = _31 * other._13 + _32 * other._23 + _33 * other._33;
-        return ret;
-    }
-
-    Vec3 matmul(const Vec3& other) const{
-        Vec3 ret;
-        ret.x = _11 * other.x + _12 * other.y + _13 * other.z;
-        ret.y = _21 * other.x + _22 * other.y + _23 * other.z;
-        ret.z = _31 * other.x + _32 * other.y + _33 * other.z;
-        return ret;
-    }
-
-    bool operator==(const Mat3x3& other) const{
-        for (int i=0; i<9; ++i){
-            if (!isclose(v[i], other.v[i])) return false;
-        }
-        return true;
-    }
-
-    bool operator!=(const Mat3x3& other) const{
-        for (int i=0; i<9; ++i){
-            if (!isclose(v[i], other.v[i])) return true;
-        }
-        return false;
-    }
-
-    float determinant() const{
-        return _11 * _22 * _33 + _12 * _23 * _31 + _13 * _21 * _32
-             - _11 * _23 * _32 - _12 * _21 * _33 - _13 * _22 * _31;
-    }
-
-    Mat3x3 transpose() const{
-        Mat3x3 ret;
-        ret._11 = _11;  ret._12 = _21;  ret._13 = _31;
-        ret._21 = _12;  ret._22 = _22;  ret._23 = _32;
-        ret._31 = _13;  ret._32 = _23;  ret._33 = _33;
-        return ret;
-    }
-
-    bool inverse(Mat3x3& ret) const{
-        float det = determinant();
-        if (fabsf(det) < kEpsilon) return false;
-        float inv_det = 1.0f / det;
-        ret._11 = (_22 * _33 - _23 * _32) * inv_det;
-        ret._12 = (_13 * _32 - _12 * _33) * inv_det;
-        ret._13 = (_12 * _23 - _13 * _22) * inv_det;
-        ret._21 = (_23 * _31 - _21 * _33) * inv_det;
-        ret._22 = (_11 * _33 - _13 * _31) * inv_det;
-        ret._23 = (_13 * _21 - _11 * _23) * inv_det;
-        ret._31 = (_21 * _32 - _22 * _31) * inv_det;
-        ret._32 = (_12 * _31 - _11 * _32) * inv_det;
-        ret._33 = (_11 * _22 - _12 * _21) * inv_det;
-        return true;
-    }
-
-    /*************** affine transformations ***************/
-    static Mat3x3 trs(Vec2 t, float radian, Vec2 s){
-        float cr = cosf(radian);
-        float sr = sinf(radian);
-        return Mat3x3(s.x * cr,   -s.y * sr,  t.x,
-                      s.x * sr,   s.y * cr,   t.y,
-                      0.0f,       0.0f,       1.0f);
-    }
-
-    bool is_affine() const{
-        float det = _11 * _22 - _12 * _21;
-        if(fabsf(det) < kEpsilon) return false;
-        return _31 == 0.0f && _32 == 0.0f && _33 == 1.0f;
-    }
-
-    Mat3x3 inverse_affine() const{
-        Mat3x3 ret;
-        float det = _11 * _22 - _12 * _21;
-        float inv_det = 1.0f / det;
-        ret._11 = _22 * inv_det;
-        ret._12 = -_12 * inv_det;
-        ret._13 = (_12 * _23 - _13 * _22) * inv_det;
-        ret._21 = -_21 * inv_det;
-        ret._22 = _11 * inv_det;
-        ret._23 = (_13 * _21 - _11 * _23) * inv_det;
-        ret._31 = 0.0f;
-        ret._32 = 0.0f;
-        ret._33 = 1.0f;
-        return ret;
-    }
-
-    Mat3x3 matmul_affine(const Mat3x3& other) const{
-        Mat3x3 ret;
-        ret._11 = _11 * other._11 + _12 * other._21;
-        ret._12 = _11 * other._12 + _12 * other._22;
-        ret._13 = _11 * other._13 + _12 * other._23 + _13;
-        ret._21 = _21 * other._11 + _22 * other._21;
-        ret._22 = _21 * other._12 + _22 * other._22;
-        ret._23 = _21 * other._13 + _22 * other._23 + _23;
-        ret._31 = 0.0f;
-        ret._32 = 0.0f;
-        ret._33 = 1.0f;
-        return ret;
-    }
-
-    Vec2 translation() const { return Vec2(_13, _23); }
-    float rotation() const { return atan2f(_21, _11); }
-    Vec2 scale() const {
-        return Vec2(
-            sqrtf(_11 * _11 + _21 * _21),
-            sqrtf(_12 * _12 + _22 * _22)
-        );
-    }
-
-    Vec2 transform_point(Vec2 vec) const {
-        return Vec2(_11 * vec.x + _12 * vec.y + _13, _21 * vec.x + _22 * vec.y + _23);
-    }
-
-    Vec2 transform_vector(Vec2 vec) const {
-        return Vec2(_11 * vec.x + _12 * vec.y, _21 * vec.x + _22 * vec.y);
-    }
-};
-
-struct PyVec2;
-struct PyVec3;
-struct PyVec4;
-struct PyMat3x3;
-PyObject* py_var(VM*, Vec2);
-PyObject* py_var(VM*, const PyVec2&);
-PyObject* py_var(VM*, Vec3);
-PyObject* py_var(VM*, const PyVec3&);
-PyObject* py_var(VM*, Vec4);
-PyObject* py_var(VM*, const PyVec4&);
-PyObject* py_var(VM*, const Mat3x3&);
-PyObject* py_var(VM*, const PyMat3x3&);
-
 #define BIND_VEC_ADDR(D)   \
         vm->bind_method<0>(type, "addr", [](VM* vm, ArgsView args){         \
             PyVec##D& self = _CAST(PyVec##D&, args[0]);                     \
@@ -348,14 +45,8 @@ PyObject* py_var(VM*, const PyMat3x3&);
             return vm->None;                                                \
         }));
 
-struct PyVec2: Vec2 {
-    PY_CLASS(PyVec2, linalg, vec2)
 
-    PyVec2() : Vec2() {}
-    PyVec2(const Vec2& v) : Vec2(v) {}
-    PyVec2(const PyVec2& v) : Vec2(v) {}
-
-    static void _register(VM* vm, PyObject* mod, PyObject* type){
+    void PyVec2::_register(VM* vm, PyObject* mod, PyObject* type){
         vm->bind_constructor<3>(type, [](VM* vm, ArgsView args){
             float x = CAST_F(args[1]);
             float y = CAST_F(args[2]);
@@ -406,16 +97,8 @@ struct PyVec2: Vec2 {
         BIND_VEC_FUNCTION_0(2, length_squared)
         BIND_VEC_FUNCTION_0(2, normalize)
     }
-};
-
-struct PyVec3: Vec3 {
-    PY_CLASS(PyVec3, linalg, vec3)
-
-    PyVec3() : Vec3() {}
-    PyVec3(const Vec3& v) : Vec3(v) {}
-    PyVec3(const PyVec3& v) : Vec3(v) {}
 
-    static void _register(VM* vm, PyObject* mod, PyObject* type){
+    void PyVec3::_register(VM* vm, PyObject* mod, PyObject* type){
         vm->bind_constructor<4>(type, [](VM* vm, ArgsView args){
             float x = CAST_F(args[1]);
             float y = CAST_F(args[2]);
@@ -456,16 +139,8 @@ struct PyVec3: Vec3 {
         BIND_VEC_FUNCTION_0(3, length_squared)
         BIND_VEC_FUNCTION_0(3, normalize)
     }
-};
-
-struct PyVec4: Vec4{
-    PY_CLASS(PyVec4, linalg, vec4)
-
-    PyVec4(): Vec4(){}
-    PyVec4(const Vec4& v): Vec4(v){}
-    PyVec4(const PyVec4& v): Vec4(v){}
 
-    static void _register(VM* vm, PyObject* mod, PyObject* type){
+    void PyVec4::_register(VM* vm, PyObject* mod, PyObject* type){
         vm->bind_constructor<1+4>(type, [](VM* vm, ArgsView args){
             float x = CAST_F(args[1]);
             float y = CAST_F(args[2]);
@@ -507,16 +182,15 @@ struct PyVec4: Vec4{
         BIND_VEC_FUNCTION_0(4, length_squared)
         BIND_VEC_FUNCTION_0(4, normalize)
     }
-};
 
-struct PyMat3x3: Mat3x3{
-    PY_CLASS(PyMat3x3, linalg, mat3x3)
+#undef BIND_VEC_ADDR
+#undef BIND_VEC_VEC_OP
+#undef BIND_VEC_FLOAT_OP
+#undef BIND_VEC_FIELD
+#undef BIND_VEC_FUNCTION_0
+#undef BIND_VEC_FUNCTION_1
 
-    PyMat3x3(): Mat3x3(){}
-    PyMat3x3(const Mat3x3& other): Mat3x3(other){}
-    PyMat3x3(const PyMat3x3& other): Mat3x3(other){}
-
-    static void _register(VM* vm, PyObject* mod, PyObject* type){
+    void PyMat3x3::_register(VM* vm, PyObject* mod, PyObject* type){
         vm->bind_constructor<-1>(type, [](VM* vm, ArgsView args){
             if(args.size() == 1+0) return VAR_T(PyMat3x3, Mat3x3::zeros());
             if(args.size() == 1+9){
@@ -768,44 +442,5 @@ struct PyMat3x3: Mat3x3{
             return VAR_T(PyVec2, self.transform_vector(v));
         });
     }
-};
-
-inline PyObject* py_var(VM* vm, Vec2 obj){ return VAR_T(PyVec2, obj); }
-inline PyObject* py_var(VM* vm, const PyVec2& obj){ return VAR_T(PyVec2, obj);}
-
-inline PyObject* py_var(VM* vm, Vec3 obj){ return VAR_T(PyVec3, obj); }
-inline PyObject* py_var(VM* vm, const PyVec3& obj){ return VAR_T(PyVec3, obj);}
-
-inline PyObject* py_var(VM* vm, Vec4 obj){ return VAR_T(PyVec4, obj); }
-inline PyObject* py_var(VM* vm, const PyVec4& obj){ return VAR_T(PyVec4, obj);}
-
-inline PyObject* py_var(VM* vm, const Mat3x3& obj){ return VAR_T(PyMat3x3, obj); }
-inline PyObject* py_var(VM* vm, const PyMat3x3& obj){ return VAR_T(PyMat3x3, obj); }
-
-template<> inline Vec2 py_cast<Vec2>(VM* vm, PyObject* obj) { return CAST(PyVec2&, obj); }
-template<> inline Vec3 py_cast<Vec3>(VM* vm, PyObject* obj) { return CAST(PyVec3&, obj); }
-template<> inline Vec4 py_cast<Vec4>(VM* vm, PyObject* obj) { return CAST(PyVec4&, obj); }
-template<> inline Mat3x3 py_cast<Mat3x3>(VM* vm, PyObject* obj) { return CAST(PyMat3x3&, obj); }
-
-template<> inline Vec2 _py_cast<Vec2>(VM* vm, PyObject* obj) { return _CAST(PyVec2&, obj); }
-template<> inline Vec3 _py_cast<Vec3>(VM* vm, PyObject* obj) { return _CAST(PyVec3&, obj); }
-template<> inline Vec4 _py_cast<Vec4>(VM* vm, PyObject* obj) { return _CAST(PyVec4&, obj); }
-template<> inline Mat3x3 _py_cast<Mat3x3>(VM* vm, PyObject* obj) { return _CAST(PyMat3x3&, obj); }
-
-inline void add_module_linalg(VM* vm){
-    PyObject* linalg = vm->new_module("linalg");
-    PyVec2::register_class(vm, linalg);
-    PyVec3::register_class(vm, linalg);
-    PyVec4::register_class(vm, linalg);
-    PyMat3x3::register_class(vm, linalg);
-}
-
-static_assert(sizeof(Py_<PyMat3x3>) <= 64);
-
-}   // namespace pkpy
-
-#else
-
-ADD_MODULE_PLACEHOLDER(linalg)
 
-#endif
+}   // namespace pkpy

+ 1 - 1
src/main.cpp

@@ -1,7 +1,7 @@
 #include <fstream>
 #include <filesystem>
 
-#include "pocketpy.h"
+#include "pocketpy/pocketpy.h"
 
 #ifndef __EMSCRIPTEN__
 

+ 23 - 0
src/namedict.cpp

@@ -0,0 +1,23 @@
+#include "pocketpy/namedict.h"
+
+namespace pkpy{
+
+uint16_t _find_perfect_hash_seed(uint16_t capacity, const std::vector<StrName>& keys){
+    if(keys.empty()) return kHashSeeds[0];
+    static std::set<uint16_t> indices;
+    indices.clear();
+    std::pair<uint16_t, float> best_score = {kHashSeeds[0], 0.0f};
+    const int kHashSeedsSize = sizeof(kHashSeeds) / sizeof(kHashSeeds[0]);
+    for(int i=0; i<kHashSeedsSize; i++){
+        indices.clear();
+        for(auto key: keys){
+            uint16_t index = _hash(key, capacity-1, kHashSeeds[i]);
+            indices.insert(index);
+        }
+        float score = indices.size() / (float)keys.size();
+        if(score > best_score.second) best_score = {kHashSeeds[i], score};
+    }
+    return best_score.first;
+}
+
+}   // namespace pkpy

+ 15 - 171
src/pocketpy.h → src/pocketpy.cpp

@@ -1,38 +1,8 @@
-#pragma once
-
-#include "ceval.h"
-#include "compiler.h"
-#include "obj.h"
-#include "repl.h"
-#include "iter.h"
-#include "base64.h"
-#include "cffi.h"
-#include "linalg.h"
-#include "easing.h"
-#include "io.h"
-#include "_generated.h"
-#include "export.h"
-#include "vm.h"
-#include "re.h"
-#include "random.h"
-
-namespace pkpy {
-
-inline CodeObject_ VM::compile(Str source, Str filename, CompileMode mode, bool unknown_global_scope) {
-    Compiler compiler(this, source, filename, mode, unknown_global_scope);
-    try{
-        return compiler.compile();
-    }catch(Exception& e){
-#if PK_DEBUG_FULL_EXCEPTION
-        std::cerr << e.summary() << std::endl;
-#endif
-        _error(e);
-        return nullptr;
-    }
-}
+#include "pocketpy/pocketpy.h"
 
+namespace pkpy{
 
-inline void init_builtins(VM* _vm) {
+void init_builtins(VM* _vm) {
 #define BIND_NUM_ARITH_OPT(name, op)                                                                    \
     _vm->bind##name(_vm->tp_int, [](VM* vm, PyObject* lhs, PyObject* rhs) {                             \
         if(is_int(rhs)) return VAR(_CAST(i64, lhs) op _CAST(i64, rhs));                                 \
@@ -1178,7 +1148,8 @@ inline void init_builtins(VM* _vm) {
     Generator::register_class(_vm, _vm->builtins);
 }
 
-inline void add_module_timeit(VM* vm){
+
+void add_module_timeit(VM* vm){
     PyObject* mod = vm->new_module("timeit");
     vm->bind_func<2>(mod, "timeit", [](VM* vm, ArgsView args) {
         PyObject* f = args[0];
@@ -1191,7 +1162,7 @@ inline void add_module_timeit(VM* vm){
     });
 }
 
-inline void add_module_time(VM* vm){
+void add_module_time(VM* vm){
     PyObject* mod = vm->new_module("time");
     vm->bind_func<0>(mod, "time", [](VM* vm, ArgsView args) {
         auto now = std::chrono::system_clock::now();
@@ -1227,57 +1198,8 @@ inline void add_module_time(VM* vm){
     });
 }
 
-struct PyREPL{
-    PY_CLASS(PyREPL, sys, _repl)
-
-    REPL* repl;
-
-    PyREPL(VM* vm){ repl = new REPL(vm); }
-    ~PyREPL(){ delete repl; }
-
-    PyREPL(const PyREPL&) = delete;
-    PyREPL& operator=(const PyREPL&) = delete;
-
-    PyREPL(PyREPL&& other) noexcept{
-        repl = other.repl;
-        other.repl = nullptr;
-    }
-
-    struct TempOut{
-        PrintFunc backup;
-        VM* vm;
-        TempOut(VM* vm, PrintFunc f){
-            this->vm = vm;
-            this->backup = vm->_stdout;
-            vm->_stdout = f;
-        }
-        ~TempOut(){
-            vm->_stdout = backup;
-        }
-        TempOut(const TempOut&) = delete;
-        TempOut& operator=(const TempOut&) = delete;
-        TempOut(TempOut&&) = delete;
-        TempOut& operator=(TempOut&&) = delete;
-    };
-
-    static void _register(VM* vm, PyObject* mod, PyObject* type){
-        vm->bind_constructor<1>(type, [](VM* vm, ArgsView args){
-            return VAR_T(PyREPL, vm);
-        });
-
-        vm->bind_method<1>(type, "input", [](VM* vm, ArgsView args){
-            PyREPL& self = _CAST(PyREPL&, args[0]);
-            const Str& s = CAST(Str&, args[1]);
-            static std::stringstream ss_out;
-            ss_out.str("");
-            TempOut _(vm, [](VM* vm, const Str& s){ ss_out << s; });
-            bool ok = self.repl->input(s.str());
-            return VAR(Tuple({VAR(ok), VAR(ss_out.str())}));
-        });
-    }
-};
 
-inline void add_module_sys(VM* vm){
+void add_module_sys(VM* vm){
     PyObject* mod = vm->new_module("sys");
     PyREPL::register_class(vm, mod);
     vm->setattr(mod, "version", VAR(PK_VERSION));
@@ -1298,7 +1220,7 @@ inline void add_module_sys(VM* vm){
     });
 }
 
-inline void add_module_json(VM* vm){
+void add_module_json(VM* vm){
     PyObject* mod = vm->new_module("json");
     vm->bind_func<1>(mod, "loads", [](VM* vm, ArgsView args) {
         const Str& expr = CAST(Str&, args[0]);
@@ -1313,7 +1235,7 @@ inline void add_module_json(VM* vm){
 
 
 // https://docs.python.org/3.5/library/math.html
-inline void add_module_math(VM* vm){
+void add_module_math(VM* vm){
     PyObject* mod = vm->new_module("math");
     mod->attr().set("pi", VAR(3.1415926535897932384));
     mod->attr().set("e" , VAR(2.7182818284590452354));
@@ -1388,7 +1310,7 @@ inline void add_module_math(VM* vm){
     });
 }
 
-inline void add_module_traceback(VM* vm){
+void add_module_traceback(VM* vm){
     PyObject* mod = vm->new_module("traceback");
     vm->bind_func<0>(mod, "print_exc", [](VM* vm, ArgsView args) {
         if(vm->_last_exception==nullptr) vm->ValueError("no exception");
@@ -1404,7 +1326,7 @@ inline void add_module_traceback(VM* vm){
     });
 }
 
-inline void add_module_dis(VM* vm){
+void add_module_dis(VM* vm){
     PyObject* mod = vm->new_module("dis");
 
     static const auto get_code = [](VM* vm, PyObject* obj)->CodeObject_{
@@ -1429,12 +1351,13 @@ inline void add_module_dis(VM* vm){
     });
 }
 
-inline void add_module_gc(VM* vm){
+void add_module_gc(VM* vm){
     PyObject* mod = vm->new_module("gc");
     vm->bind_func<0>(mod, "collect", PK_LAMBDA(VAR(vm->heap.collect())));
 }
 
-inline void VM::post_init(){
+
+void VM::post_init(){
     init_builtins(this);
 
     _t(tp_object)->attr().set("__class__", property(PK_LAMBDA(vm->_t(args[0]))));
@@ -1513,83 +1436,4 @@ inline void VM::post_init(){
 #endif
 }
 
-}   // namespace pkpy
-
-/*************************GLOBAL NAMESPACE*************************/
-extern "C" {
-    PK_LEGACY_EXPORT
-    void pkpy_free(void* p){
-        free(p);
-    }
-
-    PK_LEGACY_EXPORT
-    void pkpy_vm_exec(pkpy::VM* vm, const char* source){
-        vm->exec(source, "main.py", pkpy::EXEC_MODE);
-    }
-
-    PK_LEGACY_EXPORT
-    void pkpy_vm_exec_2(pkpy::VM* vm, const char* source, const char* filename, int mode, const char* module){
-        pkpy::PyObject* mod;
-        if(module == nullptr) mod = vm->_main;
-        else{
-            mod = vm->_modules.try_get(module);
-            if(mod == nullptr) return;
-        }
-        vm->exec(source, filename, (pkpy::CompileMode)mode, mod);
-    }
-
-    PK_LEGACY_EXPORT
-    void pkpy_vm_compile(pkpy::VM* vm, const char* source, const char* filename, int mode, bool* ok, char** res){
-        try{
-            pkpy::CodeObject_ code = vm->compile(source, filename, (pkpy::CompileMode)mode);
-            *res = code->serialize(vm).c_str_dup();
-            *ok = true;
-        }catch(pkpy::Exception& e){
-            *ok = false;
-            *res = e.summary().c_str_dup();
-        }catch(std::exception& e){
-            *ok = false;
-            *res = strdup(e.what());
-        }catch(...){
-            *ok = false;
-            *res = strdup("unknown error");
-        }
-    }
-
-    PK_LEGACY_EXPORT
-    pkpy::REPL* pkpy_new_repl(pkpy::VM* vm){
-        pkpy::REPL* p = new pkpy::REPL(vm);
-        return p;
-    }
-
-    PK_LEGACY_EXPORT
-    bool pkpy_repl_input(pkpy::REPL* r, const char* line){
-        return r->input(line);
-    }
-
-    PK_LEGACY_EXPORT
-    void pkpy_vm_add_module(pkpy::VM* vm, const char* name, const char* source){
-        vm->_lazy_modules[name] = source;
-    }
-
-    PK_LEGACY_EXPORT
-    pkpy::VM* pkpy_new_vm(bool enable_os=true){
-        pkpy::VM* p = new pkpy::VM(enable_os);
-        return p;
-    }
-
-    PK_LEGACY_EXPORT
-    void pkpy_delete_vm(pkpy::VM* vm){
-        delete vm;
-    }
-
-    PK_LEGACY_EXPORT
-    void pkpy_delete_repl(pkpy::REPL* repl){
-        delete repl;
-    }
-
-    PK_LEGACY_EXPORT
-    void pkpy_vm_gc_on_delete(pkpy::VM* vm, void (*f)(pkpy::VM *, pkpy::PyObject *)){
-        vm->heap._gc_on_delete = f;
-    }
-}
+}   // namespace pkpy

+ 242 - 0
src/str.cpp

@@ -0,0 +1,242 @@
+#include "pocketpy/str.h"
+
+namespace pkpy {
+
+    Str& Str::operator=(const Str& other){
+        if(!is_inlined()) pool64.dealloc(data);
+        size = other.size;
+        is_ascii = other.is_ascii;
+        _alloc();
+        memcpy(data, other.data, size);
+        return *this;
+    }
+
+    Str Str::operator+(const Str& other) const {
+        Str ret(size + other.size, is_ascii && other.is_ascii);
+        memcpy(ret.data, data, size);
+        memcpy(ret.data + size, other.data, other.size);
+        return ret;
+    }
+
+    Str Str::operator+(const char* p) const {
+        Str other(p);
+        return *this + other;
+    }
+
+    bool Str::operator==(const Str& other) const {
+        if(size != other.size) return false;
+        return memcmp(data, other.data, size) == 0;
+    }
+
+    bool Str::operator!=(const Str& other) const {
+        if(size != other.size) return true;
+        return memcmp(data, other.data, size) != 0;
+    }
+
+    bool Str::operator==(const std::string_view other) const {
+        if(size != (int)other.size()) return false;
+        return memcmp(data, other.data(), size) == 0;
+    }
+
+    bool Str::operator!=(const std::string_view other) const {
+        if(size != (int)other.size()) return true;
+        return memcmp(data, other.data(), size) != 0;
+    }
+
+    bool Str::operator==(const char* p) const {
+        return *this == std::string_view(p);
+    }
+
+    bool Str::operator!=(const char* p) const {
+        return *this != std::string_view(p);
+    }
+
+    bool Str::operator<(const Str& other) const {
+        int ret = strncmp(data, other.data, std::min(size, other.size));
+        if(ret != 0) return ret < 0;
+        return size < other.size;
+    }
+
+    bool Str::operator<(const std::string_view other) const {
+        int ret = strncmp(data, other.data(), std::min(size, (int)other.size()));
+        if(ret != 0) return ret < 0;
+        return size < (int)other.size();
+    }
+
+    bool Str::operator>(const Str& other) const {
+        int ret = strncmp(data, other.data, std::min(size, other.size));
+        if(ret != 0) return ret > 0;
+        return size > other.size;
+    }
+
+    bool Str::operator<=(const Str& other) const {
+        int ret = strncmp(data, other.data, std::min(size, other.size));
+        if(ret != 0) return ret < 0;
+        return size <= other.size;
+    }
+
+    bool Str::operator>=(const Str& other) const {
+        int ret = strncmp(data, other.data, std::min(size, other.size));
+        if(ret != 0) return ret > 0;
+        return size >= other.size;
+    }
+
+    Str::~Str(){
+        if(!is_inlined()) pool64.dealloc(data);
+        if(_cached_c_str != nullptr) free((void*)_cached_c_str);
+    }
+
+    Str Str::substr(int start, int len) const {
+        Str ret(len, is_ascii);
+        memcpy(ret.data, data + start, len);
+        return ret;
+    }
+
+    Str Str::substr(int start) const {
+        return substr(start, size - start);
+    }
+
+    char* Str::c_str_dup() const {
+        char* p = (char*)malloc(size + 1);
+        memcpy(p, data, size);
+        p[size] = 0;
+        return p;
+    }
+
+    const char* Str::c_str(){
+        if(_cached_c_str == nullptr){
+            _cached_c_str = c_str_dup();
+        }
+        return _cached_c_str;
+    }
+
+    std::string_view Str::sv() const {
+        return std::string_view(data, size);
+    }
+
+    std::string Str::str() const {
+        return std::string(data, size);
+    }
+
+    Str Str::lstrip() const {
+        std::string copy(data, size);
+        copy.erase(copy.begin(), std::find_if(copy.begin(), copy.end(), [](char c) {
+            // std::isspace(c) does not working on windows (Debug)
+            return c != ' ' && c != '\t' && c != '\r' && c != '\n';
+        }));
+        return Str(copy);
+    }
+
+    Str Str::strip() const {
+        std::string copy(data, size);
+        copy.erase(copy.begin(), std::find_if(copy.begin(), copy.end(), [](char c) {
+            return c != ' ' && c != '\t' && c != '\r' && c != '\n';
+        }));
+        copy.erase(std::find_if(copy.rbegin(), copy.rend(), [](char c) {
+            return c != ' ' && c != '\t' && c != '\r' && c != '\n';
+        }).base(), copy.end());
+        return Str(copy);
+    }
+
+    Str Str::lower() const{
+        std::string copy(data, size);
+        std::transform(copy.begin(), copy.end(), copy.begin(), [](unsigned char c){ return std::tolower(c); });
+        return Str(copy);
+    }
+
+    Str Str::upper() const{
+        std::string copy(data, size);
+        std::transform(copy.begin(), copy.end(), copy.begin(), [](unsigned char c){ return std::toupper(c); });
+        return Str(copy);
+    }
+
+    Str Str::escape(bool single_quote) const {
+        std::stringstream ss;
+        ss << (single_quote ? '\'' : '"');
+        for (int i=0; i<length(); i++) {
+            char c = this->operator[](i);
+            switch (c) {
+                case '"':
+                    if(!single_quote) ss << '\\';
+                    ss << '"';
+                    break;
+                case '\'':
+                    if(single_quote) ss << '\\';
+                    ss << '\'';
+                    break;
+                case '\\': ss << '\\' << '\\'; break;
+                case '\n': ss << "\\n"; break;
+                case '\r': ss << "\\r"; break;
+                case '\t': ss << "\\t"; break;
+                default:
+                    if ('\x00' <= c && c <= '\x1f') {
+                        ss << "\\x" << std::hex << std::setw(2) << std::setfill('0') << (int)c;
+                    } else {
+                        ss << c;
+                    }
+            }
+        }
+        ss << (single_quote ? '\'' : '"');
+        return ss.str();
+    }
+
+    int Str::index(const Str& sub, int start) const {
+        auto p = std::search(data + start, data + size, sub.data, sub.data + sub.size);
+        if(p == data + size) return -1;
+        return p - data;
+    }
+
+    Str Str::replace(const Str& old, const Str& new_, int count) const {
+        std::stringstream ss;
+        int start = 0;
+        while(true){
+            int i = index(old, start);
+            if(i == -1) break;
+            ss << substr(start, i - start);
+            ss << new_;
+            start = i + old.size;
+            if(count != -1 && --count == 0) break;
+        }
+        ss << substr(start, size - start);
+        return ss.str();
+    }
+
+
+    int Str::_unicode_index_to_byte(int i) const{
+        if(is_ascii) return i;
+        int j = 0;
+        while(i > 0){
+            j += utf8len(data[j]);
+            i--;
+        }
+        return j;
+    }
+
+    int Str::_byte_index_to_unicode(int n) const{
+        if(is_ascii) return n;
+        int cnt = 0;
+        for(int i=0; i<n; i++){
+            if((data[i] & 0xC0) != 0x80) cnt++;
+        }
+        return cnt;
+    }
+
+    Str Str::u8_getitem(int i) const{
+        i = _unicode_index_to_byte(i);
+        return substr(i, utf8len(data[i]));
+    }
+
+    Str Str::u8_slice(int start, int stop, int step) const{
+        std::stringstream ss;
+        if(is_ascii){
+            for(int i=start; step>0?i<stop:i>stop; i+=step) ss << data[i];
+        }else{
+            for(int i=start; step>0?i<stop:i>stop; i+=step) ss << u8_getitem(i);
+        }
+        return ss.str();
+    }
+
+    int Str::u8_length() const {
+        return _byte_index_to_unicode(size);
+    }
+} // namespace pkpy

+ 55 - 0
src/tuplelist.cpp

@@ -0,0 +1,55 @@
+#include "pocketpy/tuplelist.h"
+
+namespace pkpy {
+
+Tuple::Tuple(int n){
+    if(n <= 3){
+        this->_args = _inlined;
+    }else{
+        this->_args = (PyObject**)pool64.alloc(n * sizeof(void*));
+    }
+    this->_size = n;
+}
+
+Tuple::Tuple(const Tuple& other): Tuple(other._size){
+    for(int i=0; i<_size; i++) _args[i] = other._args[i];
+}
+
+Tuple::Tuple(Tuple&& other) noexcept {
+    _size = other._size;
+    if(other.is_inlined()){
+        _args = _inlined;
+        for(int i=0; i<_size; i++) _args[i] = other._args[i];
+    }else{
+        _args = other._args;
+        other._args = other._inlined;
+        other._size = 0;
+    }
+}
+
+Tuple::Tuple(List&& other) noexcept {
+    _size = other.size();
+    _args = other._data;
+    other._data = nullptr;
+}
+
+Tuple::Tuple(std::initializer_list<PyObject*> list): Tuple(list.size()){
+    int i = 0;
+    for(PyObject* obj: list) _args[i++] = obj;
+}
+
+Tuple::~Tuple(){ if(!is_inlined()) pool64.dealloc(_args); }
+
+List ArgsView::to_list() const{
+    List ret(size());
+    for(int i=0; i<size(); i++) ret[i] = _begin[i];
+    return ret;
+}
+
+Tuple ArgsView::to_tuple() const{
+    Tuple ret(size());
+    for(int i=0; i<size(); i++) ret[i] = _begin[i];
+    return ret;
+}
+
+}   // namespace pkpy

+ 0 - 97
src/tuplelist.h

@@ -1,97 +0,0 @@
-#pragma once
-
-#include "common.h"
-#include "memory.h"
-#include "str.h"
-#include "vector.h"
-
-namespace pkpy {
-
-using List = pod_vector<PyObject*>;
-
-class Tuple {
-    PyObject** _args;
-    PyObject* _inlined[3];
-    int _size;
-
-    bool is_inlined() const { return _args == _inlined; }
-
-    void _alloc(int n){
-        if(n <= 3){
-            this->_args = _inlined;
-        }else{
-            this->_args = (PyObject**)pool64.alloc(n * sizeof(void*));
-        }
-        this->_size = n;
-    }
-
-public:
-    Tuple(int n){ _alloc(n); }
-
-    Tuple(const Tuple& other){
-        _alloc(other._size);
-        for(int i=0; i<_size; i++) _args[i] = other._args[i];
-    }
-
-    Tuple(Tuple&& other) noexcept {
-        _size = other._size;
-        if(other.is_inlined()){
-            _args = _inlined;
-            for(int i=0; i<_size; i++) _args[i] = other._args[i];
-        }else{
-            _args = other._args;
-            other._args = other._inlined;
-            other._size = 0;
-        }
-    }
-
-    Tuple(List&& other) noexcept {
-        _size = other.size();
-        _args = other._data;
-        other._data = nullptr;
-    }
-
-    Tuple(std::initializer_list<PyObject*> list) {
-        _alloc(list.size());
-        int i = 0;
-        for(PyObject* obj: list) _args[i++] = obj;
-    }
-
-    PyObject*& operator[](int i){ return _args[i]; }
-    PyObject* operator[](int i) const { return _args[i]; }
-
-    int size() const { return _size; }
-
-    PyObject** begin() const { return _args; }
-    PyObject** end() const { return _args + _size; }
-
-    ~Tuple(){ if(!is_inlined()) pool64.dealloc(_args); }
-};
-
-// a lightweight view for function args, it does not own the memory
-struct ArgsView{
-    PyObject** _begin;
-    PyObject** _end;
-
-    ArgsView(PyObject** begin, PyObject** end) : _begin(begin), _end(end) {}
-    ArgsView(const Tuple& t) : _begin(t.begin()), _end(t.end()) {}
-
-    PyObject** begin() const { return _begin; }
-    PyObject** end() const { return _end; }
-    int size() const { return _end - _begin; }
-    bool empty() const { return _begin == _end; }
-    PyObject* operator[](int i) const { return _begin[i]; }
-
-    List to_list() const{
-        List ret(size());
-        for(int i=0; i<size(); i++) ret[i] = _begin[i];
-        return ret;
-    }
-
-    Tuple to_tuple() const{
-        Tuple ret(size());
-        for(int i=0; i<size(); i++) ret[i] = _begin[i];
-        return ret;
-    }
-};
-}   // namespace pkpy

+ 790 - 0
src/vm.cpp

@@ -0,0 +1,790 @@
+#include "pocketpy/vm.h"
+
+namespace pkpy{
+
+
+PyObject* VM::py_negate(PyObject* obj){
+    const PyTypeInfo* ti = _inst_type_info(obj);
+    if(ti->m__neg__) return ti->m__neg__(this, obj);
+    return call_method(obj, __neg__);
+}
+
+f64 VM::num_to_float(PyObject* obj){
+    if(is_float(obj)){
+        return _CAST(f64, obj);
+    } else if (is_int(obj)){
+        return (f64)_CAST(i64, obj);
+    }
+    TypeError("expected 'int' or 'float', got " + OBJ_NAME(_t(obj)).escape());
+    return 0;
+}
+
+
+bool VM::py_bool(PyObject* obj){
+    if(is_non_tagged_type(obj, tp_bool)) return obj == True;
+    if(obj == None) return false;
+    if(is_int(obj)) return _CAST(i64, obj) != 0;
+    if(is_float(obj)) return _CAST(f64, obj) != 0.0;
+    PyObject* self;
+    PyObject* len_f = get_unbound_method(obj, __len__, &self, false);
+    if(self != PY_NULL){
+        PyObject* ret = call_method(self, len_f);
+        return CAST(i64, ret) > 0;
+    }
+    return true;
+}
+
+PyObject* VM::py_list(PyObject* it){
+    auto _lock = heap.gc_scope_lock();
+    it = py_iter(it);
+    List list;
+    PyObject* obj = py_next(it);
+    while(obj != StopIteration){
+        list.push_back(obj);
+        obj = py_next(it);
+    }
+    return VAR(std::move(list));
+}
+
+
+
+void VM::parse_int_slice(const Slice& s, int length, int& start, int& stop, int& step){
+    auto clip = [](int value, int min, int max){
+        if(value < min) return min;
+        if(value > max) return max;
+        return value;
+    };
+    if(s.step == None) step = 1;
+    else step = CAST(int, s.step);
+    if(step == 0) ValueError("slice step cannot be zero");
+    if(step > 0){
+        if(s.start == None){
+            start = 0;
+        }else{
+            start = CAST(int, s.start);
+            if(start < 0) start += length;
+            start = clip(start, 0, length);
+        }
+        if(s.stop == None){
+            stop = length;
+        }else{
+            stop = CAST(int, s.stop);
+            if(stop < 0) stop += length;
+            stop = clip(stop, 0, length);
+        }
+    }else{
+        if(s.start == None){
+            start = length - 1;
+        }else{
+            start = CAST(int, s.start);
+            if(start < 0) start += length;
+            start = clip(start, -1, length - 1);
+        }
+        if(s.stop == None){
+            stop = -1;
+        }else{
+            stop = CAST(int, s.stop);
+            if(stop < 0) stop += length;
+            stop = clip(stop, -1, length - 1);
+        }
+    }
+}
+
+i64 VM::py_hash(PyObject* obj){
+    const PyTypeInfo* ti = _inst_type_info(obj);
+    if(ti->m__hash__) return ti->m__hash__(this, obj);
+    PyObject* ret = call_method(obj, __hash__);
+    return CAST(i64, ret);
+}
+
+PyObject* VM::format(Str spec, PyObject* obj){
+    if(spec.empty()) return py_str(obj);
+    char type;
+    switch(spec.end()[-1]){
+        case 'f': case 'd': case 's':
+            type = spec.end()[-1];
+            spec = spec.substr(0, spec.length() - 1);
+            break;
+        default: type = ' '; break;
+    }
+
+    char pad_c = ' ';
+    if(spec[0] == '0'){
+        pad_c = '0';
+        spec = spec.substr(1);
+    }
+    char align;
+    if(spec[0] == '>'){
+        align = '>';
+        spec = spec.substr(1);
+    }else if(spec[0] == '<'){
+        align = '<';
+        spec = spec.substr(1);
+    }else{
+        if(is_int(obj) || is_float(obj)) align = '>';
+        else align = '<';
+    }
+
+    int dot = spec.index(".");
+    int width, precision;
+    try{
+        if(dot >= 0){
+            width = Number::stoi(spec.substr(0, dot).str());
+            precision = Number::stoi(spec.substr(dot+1).str());
+        }else{
+            width = Number::stoi(spec.str());
+            precision = -1;
+        }
+    }catch(...){
+        ValueError("invalid format specifer");
+        UNREACHABLE();
+    }
+
+    if(type != 'f' && dot >= 0) ValueError("precision not allowed in the format specifier");
+    Str ret;
+    if(type == 'f'){
+        f64 val = num_to_float(obj);
+        if(precision < 0) precision = 6;
+        std::stringstream ss;
+        ss << std::fixed << std::setprecision(precision) << val;
+        ret = ss.str();
+    }else if(type == 'd'){
+        ret = std::to_string(CAST(i64, obj));
+    }else if(type == 's'){
+        ret = CAST(Str&, obj);
+    }else{
+        ret = CAST(Str&, py_str(obj));
+    }
+    if(width > ret.length()){
+        int pad = width - ret.length();
+        std::string padding(pad, pad_c);
+        if(align == '>') ret = padding.c_str() + ret;
+        else ret = ret + padding.c_str();
+    }
+    return VAR(ret);
+}
+
+PyObject* VM::new_module(StrName name) {
+    PyObject* obj = heap._new<DummyModule>(tp_module, DummyModule());
+    obj->attr().set("__name__", VAR(name.sv()));
+    // we do not allow override in order to avoid memory leak
+    // it is because Module objects are not garbage collected
+    if(_modules.contains(name)) throw std::runtime_error("module already exists");
+    _modules.set(name, obj);
+    return obj;
+}
+
+static std::string _opcode_argstr(VM* vm, Bytecode byte, const CodeObject* co){
+    std::string argStr = byte.arg == -1 ? "" : std::to_string(byte.arg);
+    switch(byte.op){
+        case OP_LOAD_CONST:
+            if(vm != nullptr){
+                argStr += fmt(" (", CAST(Str, vm->py_repr(co->consts[byte.arg])), ")");
+            }
+            break;
+        case OP_LOAD_NAME: case OP_LOAD_GLOBAL: case OP_LOAD_NONLOCAL: case OP_STORE_GLOBAL:
+        case OP_LOAD_ATTR: case OP_LOAD_METHOD: case OP_STORE_ATTR: case OP_DELETE_ATTR:
+        case OP_IMPORT_NAME: case OP_BEGIN_CLASS: case OP_RAISE:
+        case OP_DELETE_GLOBAL: case OP_INC_GLOBAL: case OP_DEC_GLOBAL: case OP_STORE_CLASS_ATTR:
+            argStr += fmt(" (", StrName(byte.arg).sv(), ")");
+            break;
+        case OP_LOAD_FAST: case OP_STORE_FAST: case OP_DELETE_FAST: case OP_INC_FAST: case OP_DEC_FAST:
+            argStr += fmt(" (", co->varnames[byte.arg].sv(), ")");
+            break;
+        case OP_LOAD_FUNCTION:
+            argStr += fmt(" (", co->func_decls[byte.arg]->code->name, ")");
+            break;
+    }
+    return argStr;
+}
+
+Str VM::disassemble(CodeObject_ co){
+    auto pad = [](const Str& s, const int n){
+        if(s.length() >= n) return s.substr(0, n);
+        return s + std::string(n - s.length(), ' ');
+    };
+
+    std::vector<int> jumpTargets;
+    for(auto byte : co->codes){
+        if(byte.op == OP_JUMP_ABSOLUTE || byte.op == OP_POP_JUMP_IF_FALSE || byte.op == OP_SHORTCUT_IF_FALSE_OR_POP){
+            jumpTargets.push_back(byte.arg);
+        }
+    }
+    std::stringstream ss;
+    int prev_line = -1;
+    for(int i=0; i<co->codes.size(); i++){
+        const Bytecode& byte = co->codes[i];
+        Str line = std::to_string(co->lines[i]);
+        if(co->lines[i] == prev_line) line = "";
+        else{
+            if(prev_line != -1) ss << "\n";
+            prev_line = co->lines[i];
+        }
+
+        std::string pointer;
+        if(std::find(jumpTargets.begin(), jumpTargets.end(), i) != jumpTargets.end()){
+            pointer = "-> ";
+        }else{
+            pointer = "   ";
+        }
+        ss << pad(line, 8) << pointer << pad(std::to_string(i), 3);
+        ss << " " << pad(OP_NAMES[byte.op], 25) << " ";
+        // ss << pad(byte.arg == -1 ? "" : std::to_string(byte.arg), 5);
+        std::string argStr = _opcode_argstr(this, byte, co.get());
+        ss << argStr;
+        // ss << pad(argStr, 40);      // may overflow
+        // ss << co->blocks[byte.block].type;
+        if(i != co->codes.size() - 1) ss << '\n';
+    }
+
+    for(auto& decl: co->func_decls){
+        ss << "\n\n" << "Disassembly of " << decl->code->name << ":\n";
+        ss << disassemble(decl->code);
+    }
+    ss << "\n";
+    return Str(ss.str());
+}
+
+#if PK_DEBUG_CEVAL_STEP
+void VM::_log_s_data(const char* title) {
+    if(_main == nullptr) return;
+    if(callstack.empty()) return;
+    std::stringstream ss;
+    if(title) ss << title << " | ";
+    std::map<PyObject**, int> sp_bases;
+    for(Frame& f: callstack.data()){
+        if(f._sp_base == nullptr) FATAL_ERROR();
+        sp_bases[f._sp_base] += 1;
+    }
+    FrameId frame = top_frame();
+    int line = frame->co->lines[frame->_ip];
+    ss << frame->co->name << ":" << line << " [";
+    for(PyObject** p=s_data.begin(); p!=s_data.end(); p++){
+        ss << std::string(sp_bases[p], '|');
+        if(sp_bases[p] > 0) ss << " ";
+        PyObject* obj = *p;
+        if(obj == nullptr) ss << "(nil)";
+        else if(obj == PY_NULL) ss << "NULL";
+        else if(is_int(obj)) ss << CAST(i64, obj);
+        else if(is_float(obj)) ss << CAST(f64, obj);
+        else if(is_type(obj, tp_str)) ss << CAST(Str, obj).escape();
+        else if(obj == None) ss << "None";
+        else if(obj == True) ss << "True";
+        else if(obj == False) ss << "False";
+        else if(is_type(obj, tp_function)){
+            auto& f = CAST(Function&, obj);
+            ss << f.decl->code->name << "(...)";
+        } else if(is_type(obj, tp_type)){
+            Type t = PK_OBJ_GET(Type, obj);
+            ss << "<class " + _all_types[t].name.escape() + ">";
+        } else if(is_type(obj, tp_list)){
+            auto& t = CAST(List&, obj);
+            ss << "list(size=" << t.size() << ")";
+        } else if(is_type(obj, tp_tuple)){
+            auto& t = CAST(Tuple&, obj);
+            ss << "tuple(size=" << t.size() << ")";
+        } else ss << "(" << obj_type_name(this, obj->type) << ")";
+        ss << ", ";
+    }
+    std::string output = ss.str();
+    if(!s_data.empty()) {
+        output.pop_back(); output.pop_back();
+    }
+    output.push_back(']');
+    Bytecode byte = frame->co->codes[frame->_ip];
+    std::cout << output << " " << OP_NAMES[byte.op] << " " << _opcode_argstr(nullptr, byte, frame->co) << std::endl;
+}
+#endif
+
+void VM::init_builtin_types(){
+    _all_types.push_back({heap._new<Type>(Type(1), Type(0)), -1, "object", true});
+    _all_types.push_back({heap._new<Type>(Type(1), Type(1)), 0, "type", false});
+    tp_object = 0; tp_type = 1;
+
+    tp_int = _new_type_object("int");
+    tp_float = _new_type_object("float");
+    if(tp_int.index != kTpIntIndex || tp_float.index != kTpFloatIndex) FATAL_ERROR();
+
+    tp_bool = _new_type_object("bool");
+    tp_str = _new_type_object("str");
+    tp_list = _new_type_object("list");
+    tp_tuple = _new_type_object("tuple");
+    tp_slice = _new_type_object("slice");
+    tp_range = _new_type_object("range");
+    tp_module = _new_type_object("module");
+    tp_function = _new_type_object("function");
+    tp_native_func = _new_type_object("native_func");
+    tp_bound_method = _new_type_object("bound_method");
+    tp_super = _new_type_object("super");
+    tp_exception = _new_type_object("Exception");
+    tp_bytes = _new_type_object("bytes");
+    tp_mappingproxy = _new_type_object("mappingproxy");
+    tp_dict = _new_type_object("dict");
+    tp_property = _new_type_object("property");
+    tp_star_wrapper = _new_type_object("_star_wrapper");
+
+    this->None = heap._new<Dummy>(_new_type_object("NoneType"), {});
+    this->NotImplemented = heap._new<Dummy>(_new_type_object("NotImplementedType"), {});
+    this->Ellipsis = heap._new<Dummy>(_new_type_object("ellipsis"), {});
+    this->True = heap._new<Dummy>(tp_bool, {});
+    this->False = heap._new<Dummy>(tp_bool, {});
+    this->StopIteration = heap._new<Dummy>(_new_type_object("StopIterationType"), {});
+
+    this->builtins = new_module("builtins");
+    
+    // setup public types
+    builtins->attr().set("type", _t(tp_type));
+    builtins->attr().set("object", _t(tp_object));
+    builtins->attr().set("bool", _t(tp_bool));
+    builtins->attr().set("int", _t(tp_int));
+    builtins->attr().set("float", _t(tp_float));
+    builtins->attr().set("str", _t(tp_str));
+    builtins->attr().set("list", _t(tp_list));
+    builtins->attr().set("tuple", _t(tp_tuple));
+    builtins->attr().set("range", _t(tp_range));
+    builtins->attr().set("bytes", _t(tp_bytes));
+    builtins->attr().set("dict", _t(tp_dict));
+    builtins->attr().set("property", _t(tp_property));
+    builtins->attr().set("StopIteration", StopIteration);
+    builtins->attr().set("NotImplemented", NotImplemented);
+    builtins->attr().set("slice", _t(tp_slice));
+
+    post_init();
+    for(int i=0; i<_all_types.size(); i++){
+        _all_types[i].obj->attr()._try_perfect_rehash();
+    }
+    for(auto [k, v]: _modules.items()) v->attr()._try_perfect_rehash();
+    this->_main = new_module("__main__");
+}
+
+// `heap.gc_scope_lock();` needed before calling this function
+void VM::_unpack_as_list(ArgsView args, List& list){
+    for(PyObject* obj: args){
+        if(is_non_tagged_type(obj, tp_star_wrapper)){
+            const StarWrapper& w = _CAST(StarWrapper&, obj);
+            // maybe this check should be done in the compile time
+            if(w.level != 1) TypeError("expected level 1 star wrapper");
+            PyObject* _0 = py_iter(w.obj);
+            PyObject* _1 = py_next(_0);
+            while(_1 != StopIteration){
+                list.push_back(_1);
+                _1 = py_next(_0);
+            }
+        }else{
+            list.push_back(obj);
+        }
+    }
+}
+
+// `heap.gc_scope_lock();` needed before calling this function
+void VM::_unpack_as_dict(ArgsView args, Dict& dict){
+    for(PyObject* obj: args){
+        if(is_non_tagged_type(obj, tp_star_wrapper)){
+            const StarWrapper& w = _CAST(StarWrapper&, obj);
+            // maybe this check should be done in the compile time
+            if(w.level != 2) TypeError("expected level 2 star wrapper");
+            const Dict& other = CAST(Dict&, w.obj);
+            dict.update(other);
+        }else{
+            const Tuple& t = CAST(Tuple&, obj);
+            if(t.size() != 2) TypeError("expected tuple of length 2");
+            dict.set(t[0], t[1]);
+        }
+    }
+}
+
+
+void VM::_prepare_py_call(PyObject** buffer, ArgsView args, ArgsView kwargs, const FuncDecl_& decl){
+    const CodeObject* co = decl->code.get();
+    int co_nlocals = co->varnames.size();
+    int decl_argc = decl->args.size();
+
+    if(args.size() < decl_argc){
+        vm->TypeError(fmt(
+            "expected ", decl_argc, " positional arguments, got ", args.size(),
+            " (", co->name, ')'
+        ));
+    }
+
+    int i = 0;
+    // prepare args
+    for(int index: decl->args) buffer[index] = args[i++];
+    // set extra varnames to nullptr
+    for(int j=i; j<co_nlocals; j++) buffer[j] = PY_NULL;
+    // prepare kwdefaults
+    for(auto& kv: decl->kwargs) buffer[kv.key] = kv.value;
+    
+    // handle *args
+    if(decl->starred_arg != -1){
+        ArgsView vargs(args.begin() + i, args.end());
+        buffer[decl->starred_arg] = VAR(vargs.to_tuple());
+        i += vargs.size();
+    }else{
+        // kwdefaults override
+        for(auto& kv: decl->kwargs){
+            if(i >= args.size()) break;
+            buffer[kv.key] = args[i++];
+        }
+        if(i < args.size()) TypeError(fmt("too many arguments", " (", decl->code->name, ')'));
+    }
+    
+    PyObject* vkwargs;
+    if(decl->starred_kwarg != -1){
+        vkwargs = VAR(Dict(this));
+        buffer[decl->starred_kwarg] = vkwargs;
+    }else{
+        vkwargs = nullptr;
+    }
+
+    for(int j=0; j<kwargs.size(); j+=2){
+        StrName key(CAST(int, kwargs[j]));
+        int index = co->varnames_inv.try_get(key);
+        if(index < 0){
+            if(vkwargs == nullptr){
+                TypeError(fmt(key.escape(), " is an invalid keyword argument for ", co->name, "()"));
+            }else{
+                Dict& dict = _CAST(Dict&, vkwargs);
+                dict.set(VAR(key.sv()), kwargs[j+1]);
+            }
+        }else{
+            buffer[index] = kwargs[j+1];
+        }
+    }
+}
+
+PyObject* VM::vectorcall(int ARGC, int KWARGC, bool op_call){
+    PyObject** p1 = s_data._sp - KWARGC*2;
+    PyObject** p0 = p1 - ARGC - 2;
+    // [callable, <self>, args..., kwargs...]
+    //      ^p0                    ^p1      ^_sp
+    PyObject* callable = p1[-(ARGC + 2)];
+    bool method_call = p1[-(ARGC + 1)] != PY_NULL;
+
+    // handle boundmethod, do a patch
+    if(is_non_tagged_type(callable, tp_bound_method)){
+        if(method_call) FATAL_ERROR();
+        auto& bm = CAST(BoundMethod&, callable);
+        callable = bm.func;      // get unbound method
+        p1[-(ARGC + 2)] = bm.func;
+        p1[-(ARGC + 1)] = bm.self;
+        method_call = true;
+        // [unbound, self, args..., kwargs...]
+    }
+
+    ArgsView args(p1 - ARGC - int(method_call), p1);
+    ArgsView kwargs(p1, s_data._sp);
+
+    static THREAD_LOCAL PyObject* buffer[PK_MAX_CO_VARNAMES];
+
+    if(is_non_tagged_type(callable, tp_native_func)){
+        const auto& f = PK_OBJ_GET(NativeFunc, callable);
+        PyObject* ret;
+        if(f.decl != nullptr){
+            int co_nlocals = f.decl->code->varnames.size();
+            _prepare_py_call(buffer, args, kwargs, f.decl);
+            // copy buffer back to stack
+            s_data.reset(args.begin());
+            for(int j=0; j<co_nlocals; j++) PUSH(buffer[j]);
+            ret = f.call(vm, ArgsView(s_data._sp - co_nlocals, s_data._sp));
+        }else{
+            if(KWARGC != 0) TypeError("old-style native_func does not accept keyword arguments");
+            f.check_size(this, args);
+            ret = f.call(this, args);
+        }
+        s_data.reset(p0);
+        return ret;
+    }
+
+    if(is_non_tagged_type(callable, tp_function)){
+        /*****************_py_call*****************/
+        // callable must be a `function` object
+        if(s_data.is_overflow()) StackOverflowError();
+
+        const Function& fn = PK_OBJ_GET(Function, callable);
+        const FuncDecl_& decl = fn.decl;
+        const CodeObject* co = decl->code.get();
+        int co_nlocals = co->varnames.size();
+
+        _prepare_py_call(buffer, args, kwargs, decl);
+        
+        if(co->is_generator){
+            s_data.reset(p0);
+            return _py_generator(
+                Frame(&s_data, nullptr, co, fn._module, callable),
+                ArgsView(buffer, buffer + co_nlocals)
+            );
+        }
+
+        // copy buffer back to stack
+        s_data.reset(args.begin());
+        for(int j=0; j<co_nlocals; j++) PUSH(buffer[j]);
+        callstack.emplace(&s_data, p0, co, fn._module, callable, FastLocals(co, args.begin()));
+        if(op_call) return PY_OP_CALL;
+        return _run_top_frame();
+        /*****************_py_call*****************/
+    }
+
+    if(is_non_tagged_type(callable, tp_type)){
+        if(method_call) FATAL_ERROR();
+        // [type, NULL, args..., kwargs...]
+
+        DEF_SNAME(__new__);
+        PyObject* new_f = find_name_in_mro(callable, __new__);
+        PyObject* obj;
+#if PK_DEBUG_EXTRA_CHECK
+        PK_ASSERT(new_f != nullptr);
+#endif
+        if(new_f == cached_object__new__) {
+            // fast path for object.__new__
+            Type t = PK_OBJ_GET(Type, callable);
+            obj= vm->heap.gcnew<DummyInstance>(t, {});
+        }else{
+            PUSH(new_f);
+            PUSH(PY_NULL);
+            PUSH(callable);    // cls
+            for(PyObject* o: args) PUSH(o);
+            for(PyObject* o: kwargs) PUSH(o);
+            // if obj is not an instance of callable, the behavior is undefined
+            obj = vectorcall(ARGC+1, KWARGC);
+        }
+
+        // __init__
+        PyObject* self;
+        DEF_SNAME(__init__);
+        callable = get_unbound_method(obj, __init__, &self, false);
+        if (self != PY_NULL) {
+            // replace `NULL` with `self`
+            p1[-(ARGC + 2)] = callable;
+            p1[-(ARGC + 1)] = self;
+            // [init_f, self, args..., kwargs...]
+            vectorcall(ARGC, KWARGC);
+            // We just discard the return value of `__init__`
+            // in cpython it raises a TypeError if the return value is not None
+        }else{
+            // manually reset the stack
+            s_data.reset(p0);
+        }
+        return obj;
+    }
+
+    // handle `__call__` overload
+    PyObject* self;
+    DEF_SNAME(__call__);
+    PyObject* call_f = get_unbound_method(callable, __call__, &self, false);
+    if(self != PY_NULL){
+        p1[-(ARGC + 2)] = call_f;
+        p1[-(ARGC + 1)] = self;
+        // [call_f, self, args..., kwargs...]
+        return vectorcall(ARGC, KWARGC, false);
+    }
+    TypeError(OBJ_NAME(_t(callable)).escape() + " object is not callable");
+    return nullptr;
+}
+
+
+
+
+
+// https://docs.python.org/3/howto/descriptor.html#invocation-from-an-instance
+PyObject* VM::getattr(PyObject* obj, StrName name, bool throw_err){
+    PyObject* objtype;
+    // handle super() proxy
+    if(is_non_tagged_type(obj, tp_super)){
+        const Super& super = PK_OBJ_GET(Super, obj);
+        obj = super.first;
+        objtype = _t(super.second);
+    }else{
+        objtype = _t(obj);
+    }
+    PyObject* cls_var = find_name_in_mro(objtype, name);
+    if(cls_var != nullptr){
+        // handle descriptor
+        if(is_non_tagged_type(cls_var, tp_property)){
+            const Property& prop = _CAST(Property&, cls_var);
+            return call(prop.getter, obj);
+        }
+    }
+    // handle instance __dict__
+    if(!is_tagged(obj) && obj->is_attr_valid()){
+        PyObject* val = obj->attr().try_get(name);
+        if(val != nullptr) return val;
+    }
+    if(cls_var != nullptr){
+        // bound method is non-data descriptor
+        if(is_non_tagged_type(cls_var, tp_function) || is_non_tagged_type(cls_var, tp_native_func)){
+            return VAR(BoundMethod(obj, cls_var));
+        }
+        return cls_var;
+    }
+    if(throw_err) AttributeError(obj, name);
+    return nullptr;
+}
+
+// used by OP_LOAD_METHOD
+// try to load a unbound method (fallback to `getattr` if not found)
+PyObject* VM::get_unbound_method(PyObject* obj, StrName name, PyObject** self, bool throw_err, bool fallback){
+    *self = PY_NULL;
+    PyObject* objtype;
+    // handle super() proxy
+    if(is_non_tagged_type(obj, tp_super)){
+        const Super& super = PK_OBJ_GET(Super, obj);
+        obj = super.first;
+        objtype = _t(super.second);
+    }else{
+        objtype = _t(obj);
+    }
+    PyObject* cls_var = find_name_in_mro(objtype, name);
+
+    if(fallback){
+        if(cls_var != nullptr){
+            // handle descriptor
+            if(is_non_tagged_type(cls_var, tp_property)){
+                const Property& prop = _CAST(Property&, cls_var);
+                return call(prop.getter, obj);
+            }
+        }
+        // handle instance __dict__
+        if(!is_tagged(obj) && obj->is_attr_valid()){
+            PyObject* val = obj->attr().try_get(name);
+            if(val != nullptr) return val;
+        }
+    }
+
+    if(cls_var != nullptr){
+        if(is_non_tagged_type(cls_var, tp_function) || is_non_tagged_type(cls_var, tp_native_func)){
+            *self = obj;
+        }
+        return cls_var;
+    }
+    if(throw_err) AttributeError(obj, name);
+    return nullptr;
+}
+
+void VM::setattr(PyObject* obj, StrName name, PyObject* value){
+    PyObject* objtype;
+    // handle super() proxy
+    if(is_non_tagged_type(obj, tp_super)){
+        Super& super = PK_OBJ_GET(Super, obj);
+        obj = super.first;
+        objtype = _t(super.second);
+    }else{
+        objtype = _t(obj);
+    }
+    PyObject* cls_var = find_name_in_mro(objtype, name);
+    if(cls_var != nullptr){
+        // handle descriptor
+        if(is_non_tagged_type(cls_var, tp_property)){
+            const Property& prop = _CAST(Property&, cls_var);
+            if(prop.setter != vm->None){
+                call(prop.setter, obj, value);
+            }else{
+                TypeError(fmt("readonly attribute: ", name.escape()));
+            }
+            return;
+        }
+    }
+    // handle instance __dict__
+    if(is_tagged(obj) || !obj->is_attr_valid()) TypeError("cannot set attribute");
+    obj->attr().set(name, value);
+}
+
+PyObject* VM::bind(PyObject* obj, const char* sig, NativeFuncC fn){
+    return bind(obj, sig, nullptr, fn);
+}
+
+PyObject* VM::bind(PyObject* obj, const char* sig, const char* docstring, NativeFuncC fn){
+    CodeObject_ co;
+    try{
+        // fn(a, b, *c, d=1) -> None
+        co = compile("def " + Str(sig) + " : pass", "<bind>", EXEC_MODE);
+    }catch(Exception& e){
+        throw std::runtime_error("invalid signature: " + std::string(sig));
+    }
+    if(co->func_decls.size() != 1){
+        throw std::runtime_error("expected 1 function declaration");
+    }
+    FuncDecl_ decl = co->func_decls[0];
+    decl->signature = Str(sig);
+    if(docstring != nullptr){
+        decl->docstring = Str(docstring).strip();
+    }
+    PyObject* f_obj = VAR(NativeFunc(fn, decl));
+    obj->attr().set(decl->code->name, f_obj);
+    return f_obj;
+}
+
+void VM::_error(Exception e){
+    if(callstack.empty()){
+        e.is_re = false;
+        throw e;
+    }
+    PUSH(VAR(e));
+    _raise();
+}
+
+void ManagedHeap::mark() {
+    for(PyObject* obj: _no_gc) PK_OBJ_MARK(obj);
+    for(auto& frame : vm->callstack.data()) frame._gc_mark();
+    for(PyObject* obj: vm->s_data) PK_OBJ_MARK(obj);
+    if(_gc_marker_ex) _gc_marker_ex(vm);
+    if(vm->_last_exception) PK_OBJ_MARK(vm->_last_exception);
+}
+
+Str obj_type_name(VM *vm, Type type){
+    return vm->_all_types[type].name;
+}
+
+
+void VM::bind__hash__(Type type, i64 (*f)(VM*, PyObject*)){
+    PyObject* obj = _t(type);
+    _all_types[type].m__hash__ = f;
+    PyObject* nf = bind_method<0>(obj, "__hash__", [](VM* vm, ArgsView args){
+        i64 ret = lambda_get_userdata<i64(*)(VM*, PyObject*)>(args.begin())(vm, args[0]);
+        return VAR(ret);
+    });
+    PK_OBJ_GET(NativeFunc, nf).set_userdata(f);
+}
+
+void VM::bind__len__(Type type, i64 (*f)(VM*, PyObject*)){
+    PyObject* obj = _t(type);
+    _all_types[type].m__len__ = f;
+    PyObject* nf = bind_method<0>(obj, "__len__", [](VM* vm, ArgsView args){
+        i64 ret = lambda_get_userdata<i64(*)(VM*, PyObject*)>(args.begin())(vm, args[0]);
+        return VAR(ret);
+    });
+    PK_OBJ_GET(NativeFunc, nf).set_userdata(f);
+}
+
+void Dict::_probe(PyObject *key, bool &ok, int &i) const{
+    ok = false;
+    i = vm->py_hash(key) & _mask;
+    while(_items[i].first != nullptr) {
+        if(vm->py_equals(_items[i].first, key)) { ok = true; break; }
+        // https://github.com/python/cpython/blob/3.8/Objects/dictobject.c#L166
+        i = ((5*i) + 1) & _mask;
+    }
+}
+
+void CodeObjectSerializer::write_object(VM *vm, PyObject *obj){
+    if(is_int(obj)) write_int(_CAST(i64, obj));
+    else if(is_float(obj)) write_float(_CAST(f64, obj));
+    else if(is_type(obj, vm->tp_str)) write_str(_CAST(Str&, obj));
+    else if(is_type(obj, vm->tp_bool)) write_bool(_CAST(bool, obj));
+    else if(obj == vm->None) write_none();
+    else if(obj == vm->Ellipsis) write_ellipsis();
+    else{
+        throw std::runtime_error(fmt(OBJ_NAME(vm->_t(obj)).escape(), " is not serializable"));
+    }
+}
+
+void NativeFunc::check_size(VM* vm, ArgsView args) const{
+    if(args.size() != argc && argc != -1) {
+        vm->TypeError(fmt("expected ", argc, " arguments, got ", args.size()));
+    }
+}
+
+PyObject* NativeFunc::call(VM *vm, ArgsView args) const {
+    return f(vm, args);
+}
+
+}   // namespace pkpy