2. 面向對象的腳本語言的類的實現
只要是一個對象就要有一個ObjHeader結構體, 該結構體位於該對象的開頭
ObjHeader結構
// 以Obj開頭的一般為對象, 但是這里ObjHeader僅僅是一個對象頭, 不是一個對象, 發現一個規律
// 在結構體中, 如果有定義一個什么type類型的, 則在該腳本語言中就不會定義成對象
typedef struct ObjHeader {
ObjType type; // 對象類型
bool isDark; // 是否可以到達, 如果可以到達, 則GC回收對象
Class *class; // 指向類對象, 在類對象中保存着方法, 這樣該對象就可以調用方法了:
struct ObjHeader *next; // 用於鏈表
} ObjHeader;
// 對象類型
typedef enum ObjType {
ObjTypeList,
ObjTypeMap,
ObjTypeModule,
ObjTypeString,
ObjTypeRange,
ObjTypeFunction,
ObjTypeThread,
ObjTypeClass,
ObjTypeInstance
} ObjType;
Value結構體(Value不是對象, 他在腳本語言層面是一個引用, 因為沒有類型, 但是在C語言中需要Value保存屬性)
// 它類似於Python中的引用, 在棧中定義, 所以腳本語言模擬的棧就是Value數組, 對象在堆中創建
typedef struct Value {
ValueType type;
union {
double num;
ObjHeader *obj_header;
};
} Value;
// 定義的類型是直接在引用右側寫出來的
// num, true, false這些都能在右側直接寫出來, 而不需要使用其他方法調用
typedef enum {
ValueTypeUndefined,
ValueTypeNull,
ValueTypeObj,
ValueTypeNum,
ValueTypeTrue,
ValueTypeFalse
} ValueType;
// 通過宏將ValueType與Value結構體直接的轉換更快捷
Class類對象結構體
/*
好好想一下, 一個類中都有什么, 這與我們在Java和C++編程的類不同, 我們只找所有的類的共同點
1. 對象頭
2. 字段個數
3. 方法對象區, 用於存方法
*/
typedef struct Class {
ObjHeader obj_header;
struct Class *superclass;
int field_num;
MethodBuffer methods;
ObjString name;
} Class;
typedef struct Method {
MethodType type;
union {
// C語言實現的方法
Primitive prim_fn;
// 腳本語言將代碼編譯成ObjClosure對象
ObjClosure *obj;
};
} Method;
typedef num MethodType {
MethodTypeNull,
MethodPrimitive,
MethodScript,
MethodCall // 用於重載
} MethodType;
在構建出上述一個類關系之后, 首先應該定義字符串類(ObjString)
// 這里僅僅是定義了字符串對象, obj_header指向是ObjString類對象
typedef struct ObjString {
ObjHeader obj_header;
long hash_code; // 保存hash值
int len;
char *start[0];
} ObjString;
// 計算字符串的hashcode
int hash_string(const char *str, int length) {
int hashcode = xxxxxxxx;
int idx = 0;
while (idx < length) {
hashcode ^= str[idx++];
hashcode *= yyyyyyyy;
}
return hashcode;
}
元對象
typedef struct {
ObjHeader obj_header;
StringBuffer module_var_name;
ValueBuffer module_var_value;
ObjString *name;
} ObjModule; // 模塊不屬於任何類, 所有它的obj_header中的class指着指向NULL
typedef struct {
ObjHeader obj_header;
Value field[0]; // 存儲屬性, 為引用, 這里是在內存中的樣子
} ObjInstance;
在腳本中執行過程中最重要的就是代碼(存放邏輯的地方, 函數, 方法, 模塊中都是)
注意: 接下來的對象結構會比較復雜, 請大致瀏覽一遍, 在后面會總結他們的關系
- 統一使用ObjFunc表示還這些代碼指令
typedef struct ObjFunc {
ObjHeader obj_header;
ByteBuffer instr_stream; // 保存編譯后的代碼指令, 這是ObjFunc對象的核心功能
ValueBuffer constants; // 常量, 在模塊中會有
Module *mod; // 屬於哪個模塊
int max_stack_size; // 可用的最大棧個數
int upvalue_num; // 用到外層函數變量的個數, 其中upvalue是一個閉包對象, 對在外層函數中棧中的被內層嵌套函數引用到的引用(Value)的封裝[為什么? 因為對象在堆中, Value這種應用類型才在棧中:-)], 可以將upvalue看成一個容器, 里面維護着Value類型的值
// 發現在ObjFunc中沒有與其對應的upvalue產生聯系, 在后面提到的ObjClosure對象中會進行關聯
int arg_num;
} ObjFunc;
- 與ObjFunc對象相關的與閉包有關的對象結構
typedef struct ObjUpValue {
ObjHeader obj_header;
Value *ptr; // 指向在外層函數中棧中的局部變量
Value closed_value; // 如果外層函數生命周期結束, 則會回收棧, 為了實現閉包, 將ptr指向的值拷貝到closed_value中即可
} ObjUpValue;
typedef struct ObjClosure {
ObjHeader *obj_header;
ObjFunc *func;
// 在這里對func與他的upvalue進行了關聯
ObjUpvalue *upvalues[0];
} ObjClosure;
函數要運行就需要一個環境, 這個環境就是一個棧幀(Frame)
// Frame就是一個函數調用框架, 就是一個棧, 但是又是有一點抽象的, 它通過start_stack來訪問Value數組
typedef struct Frame {
int *ip; // 模擬CPU的CS:IP
Value *stack_start;
/* 在上面我們提到了很多的結構體對象, 有ObjFunc, ObjUpvalue, ObjClosure, 那么到底那個才是接口, 這里Closure最大, 所以Closure是接口, 在Method結構體對象中可以看到, 在union中primitive與closure是並列的*/
ObjClosure *closure;
}Frame;
關系總結
- Frame獲取到ObjClosure, 得到ObjFunc中的intr_stream執行指令
提到了這么多的結構體, 那么創建他們的順序是怎樣的呢
-
創建vm目錄
typedef struct vm { Parser *cur_parser; // 當前vm使用的parser uint32_t allocated_bytes; // 記錄已經分配的內存空間 ObjHeader *all_objects; // 是所有ObjHeader連接成的鏈表的頭 StringTable all_method_names; // 存放方法的所有名稱, 因為從用戶中讀取到一個對象要調用一個方法, 這個是字符串的層面, 我們需要構建出一張符號表, 通過查找該字符在表中的index, 對應的映射到methods中index調用方法 ObjMap *allModules; // 通過map管理名稱與模塊 ObjThread *cur_thread; // vm支持多線程, cur_thread表示當前的線程(用戶態下就是協程) // 所有內置類的類對象指針都放在這里 Class *class_class; // 指向類的類, 是所有元類的基類和元類, 這個需要記住, class_class的元類就是他自己 Class *object_class; // 除了元類, 是所有類的基類, object_class也是class_class的基類, object_class沒有基類 Class *string_class; Class *list_class; Class *range_class; Class *thread_class; Class *map_class; /* 下面三個類他們的實現與其他不同, 他們會比較簡單, 也沒有必要通過復雜的對象來創建 */ Class *num_class; Class *null_class; Class *bool_class; } VM;
-
創建object目錄
-
在obj_header.h中創建ObjType枚舉, ObjHeader結構體, ValueType枚舉, Value結構體
-
// 對象類型 typedef enum ObjType { ObjTypeList, ObjTypeMap, ObjTypeModule, ObjTypeString, ObjTypeRange, ObjTypeFunction, ObjTypeThread, ObjTypeClass, ObjTypeInstance } ObjType; typedef struct ObjHeader { ObjType type; // 對象類型 bool isDark; // 是否可以到達, 如果可以到達, 則GC回收對象 Class *class; // 指向類對象, 在類對象中保存着方法, 這樣該對象就可以調用方法了: struct ObjHeader *next; // 用於鏈表 } ObjHeader; // 定義的類型是直接在引用右側寫出來的 // num, true, false這些都能在右側直接寫出來, 而不需要使用其他方法調用 typedef enum { ValueTypeUndefined, ValueTypeNull, ValueTypeObj, ValueTypeNum, ValueTypeTrue, // true和false主要用於map中的開放定制法 ValueTypeFalse } ValueType; // 它類似於Python中的引用, 在棧中定義, 所以腳本語言模擬的棧就是Value數組, 對象在堆中創建 typedef struct Value { ValueType type; union { double num; ObjHeader *obj_header; // obj_header的實體在對象中, 這里只需要指向對象頭即可 }; } Value;
// 通過宏將ValueType與Value結構體直接的轉換更快捷
// 此外還要定義Value之間比較的函數
valueIsEquals
思路:
Value的類型不同則false
Value的類型相同且為數字, 則直接比較數字
Value的類型相同都為Obj, 則比較里面的ObjHeader的類型, 如果相同則再看ObjHeader的類型是什么, 只能比較字符串, range和Class對象, 因為Class有類名屬性, 就相當於比較字符串
+ 緊接着創建類對象, 創建class.h文件
```c
/*
好好想一下, 一個類中都有什么, 這與我們在Java和C++編程的類不同, 我們只找所有的類的共同點
1. 對象頭
2. 字段個數
3. 方法對象區, 用於存方法
*/
typedef struct Class {
ObjHeader obj_header; // 類也是對象, 所以也會有ObjHeader, 但是其中的ObjHeader的class是指向元類的
ObjString name; // 類名
struct Class *superclass;
uint32_t field_num;
MethodBuffer methods; // 存儲Method結構體, 主要封裝了方法指針
} Class;
newVM的使用需要創建出核心模塊coreModule, 並將其添加到allModules的map中
typedef num MethodType {
MethodTypeNull,
MethodPrimitive,
MethodScript,
MethodCall // 用於重載
} MethodType;
typedef struct Method {
MethodType type;
union {
// C語言實現的方法
Primitive prim_fn;
// 腳本語言將代碼編譯成ObjClosure對象, ObjClosure包含ObjFunc對象, ObjFunc又有指令流
ObjClosure *obj;
};
} Method;
-
在有了類, 對象頭的基礎上, 緊接着創建腳本語言第一個內置對象String, 在obj_string.h中
// 這里僅僅是定義了字符串對象, obj_header指向是ObjString類對象
typedef struct ObjString {
ObjHeader obj_header;
long hash_code; // 保存hash值
int len;
char *start[0];
} ObjString;
// 在創建字符串的時候, 傳入const char *s, 使用memset拷貝過來, 不要直接用, 可能會有問題
// 計算字符串的hashcode
long hash_string(const char *str, int length) {
int hashcode = xxxxxxxx;
int idx = 0;
while (idx < length) {
hashcode ^= str[idx++];
hashcode *= yyyyyyyy;
}
return hashcode;
}
// 將計算的hash值保存到ObjString對象中
void hashObjString(ObjString &objString) {
objString->hash_code = hash_string(objString->start, objString->len);
}
```
-
有了第一個ObjString對象之后, 緊接着考慮元對象的創建, 元對象包括ObjModule和ObjInstance, ObjModule不屬於任何類, 同時需要執行一個modname, 所以需要ObjString對象, 這就是為什么需要先創建ObjString對象的原因
typedef struct objmodule { ObjHeader obj_header; // 因為mod不屬於任何類, 所有它里面的ObjHeader中的cls為NULL StringBuffer module_names; // 與module_values的長度一樣, 用於映射, 因為變量有名字和值 ValueBuffer module_values; ObjString *modname; } ObjModule; typedef struct objinstance { ObjHeader *obj_header; Value fields[0]; // 存放屬性的 } ObjInstance;
-
創建復雜的函數有關的對象, 創建obj_func.h文件
// Class對象為fnClass
typedef struct objfunc {
ObjHeader obj_header;
ByteBuffer inst_stream;
ValueBuffer constants;
int arg_num;
int upvalue_num;
int max_stack_size;
ObjModule *mod;
} ObjFunc;
typedef struct objupvalue {
ObjHeader obj_header;
Value *local_var_ptr;
Value closed_var;
struct objupvalue *next;
} ObjUpvalue;
// class對象也為fnClass
typedef struct objclosure {
ObjHeader obj_header;
ObjFunc *func;
ObjUpvalue *upvalue[0]; // 指向一個ObjUpvalue數組
} ObjClosure;
// 會讓線程對象調用
typedef struct frame {
int ip;
ObjClosure *obj_closure;
Value *stack_start;
} Frame;
注意
- Value非常的重要, 在之后函數與方法的實現都是以Value為參數和返回值得, 可以類比於Python, 但是定義一個對象的時候就不需要了, 直接一個對象上去即可, 如ObjString *objString.