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


    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.

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

  • 相关阅读:
    详解Net Core Web Api项目与在NginX下发布
    一个C#开发者重温Java的心路历程
    C#使用Consul集群进行服务注册与发现
    让我们一起揭开算法的神秘面纱
    C#调用RabbitMQ实现消息队列
    C#调用OpenCV开发简易版美图工具
    我们是如何做go语言系统测试覆盖率收集的?
    高效测试框架推荐之Ginkgo
    性能测试必知必会
    如何保障Go语言基础代码质量?
  • 原文地址:https://www.cnblogs.com/megachen/p/10383643.html
Copyright © 2020-2023  润新知