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;

关系总结

面向对象的脚本语言的类的实现-LMLPHP

  • 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文件

    /*
好好想一下, 一个类中都有什么, 这与我们在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中

    ```c

    // 这里仅仅是定义了字符串对象, 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.

其他类在后面的文章中提到

02-15 17:49