欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页  >  IT编程

深入V8引擎-AST(2)

程序员文章站 2022-06-17 08:54:26
先声明一下,这种长系列的大块头博客只能保证尽可能的深入到每一行源码,有些代码我不乐意深究就写个注释说明一下作用。另外,由于本地整理的比较好,博客就随心写了。 整个Compile过程目前只看到asmjs之前,简单的过了几遍,大部分方法没有点进去看,实在是太复杂了。上一篇的结尾指出了AST的入口,也就是 ......

  先声明一下,这种长系列的大块头博客只能保证尽可能的深入到每一行源码,有些代码我不乐意深究就写个注释说明一下作用。另外,由于本地整理的比较好,博客就随心写了。

  整个compile过程目前只看到asmjs之前,简单的过了几遍,大部分方法没有点进去看,实在是太复杂了。上一篇的结尾指出了ast的入口,也就是命名空间parsing的一个公共方法,如下。

bool parseprogram(parseinfo* info, isolate* isolate) {
  // ...
  /**
   * 生成一个parser实例
   * 调用内部方法启动转换
   */
  parser parser(info);
  functionliteral* result = nullptr;
  /**
   * 转换ast后将结果赋值给parseinfo的literal_
   */
  result = parser.parseprogram(isolate, info);
  info->set_literal(result);
  // ...
  return (result != nullptr);
}

  所需要关心的核心代码就是这些,非常简单,parser对象的初始化属性非常多,这里就不列出来了。

  接下来进入第二个核心方法,即parseprogram。

functionliteral* parser::parseprogram(isolate* isolate, parseinfo* info) {
  // ...
  /**
   * scanner_为parser类的一个私有属性
   * 这里仅仅进行初始化
   */
  scanner_.initialize();
  functionliteral* result = doparseprogram(isolate, info);

  // ...
  return result;
}

  同样,所需要关心代码只有两行,其中第一步则是启动了scanner的初始化,第二步则是开始全面解析。

  scanner包含scanner、scanner-character-strams两个部分,其中stream则是经过初步处理的源string,必须转换后才能进行解析。处理的过程在之前省略的代码中,这里稍微给出大概的转换流程。

bool parseprogram(parseinfo* info, isolate* isolate) {
  // ...
  /**
   * 1、info->script()返回的是字符串的描述信息 source是local<string>类型的源字符串
   * 2、scannerstream是scanner-character-streams头文件的类 内部方法均为静态类型 可以直接调用
   * 3、返回的具体类型根据string类型不同而不同 但是由于均继承于utf16characterstream 所以直接用父类接
   */
  handle<string> source(string::cast(info->script()->source()), isolate);
  std::unique_ptr<utf16characterstream> stream(scannerstream::for(isolate, source));
  info->set_character_stream(std::move(stream));
  // ...
}

/**
 * 有四种特殊的string类型 分别new不同的子类
 * scannerstream::for(isolate, data, 0, data->length());
 */
utf16characterstream* scannerstream::for(isolate* isolate, handle<string> data, int start_pos, int end_pos) {
  size_t start_offset = 0;
  // ...
  if (data->isseqonebytestring()) {
    return new bufferedcharacterstream<onheapstream>(
        static_cast<size_t>(start_pos), handle<seqonebytestring>::cast(data),
        start_offset, static_cast<size_t>(end_pos));
  }
}

  常规的字符串一般都是onebytestring,这里就不细讲了。最后返回一个特殊stream类,其属性记录字符串的长度、当前的解析进度、解析的开始与结束标记等等。

  将字符串转换后,就可以利用scanner来进行逐步解析,在此之前,需要对scanner类有一个简单的了解,如下。

/**
 * scanner类
 * 跟utf16characterstream一个文件
 */
class v8_export_private scanner {
  public:
    // 返回next_的token类型
    token::value peek() const { return next().token; }
    // 返回current_的位置信息
    const location& location() const { return current().location; }
  private:
    // 当前字符的unicode编码 -1表示结尾(typedef int32_t uc32)
    uc32 c0_;
    tokendesc* current_;    // desc for current token (as returned by next())
    tokendesc* next_;       // desc for next token (one token look-ahead)
    tokendesc* next_next_;  // desc for the token after next (after peakahead())
    // 从handle<string>转换后的类型 负责执行解析的实际类
    utf16characterstream* const source_;
}

  选取了一些比较简单的属性和方法,scanner内部有三个游标属性负责遍历字符串,分别是current_、next_、next_next_,字面意思理解就行了。source_则是之前说的转换stream类,所有的解析实际上都是调用这个属性的方法。而两个结构体tokendesc、location也非常重要,一个负责词法描述,一个负责记录词法位置信息,如下。

/**
 * 词法结构体
 * 每一个tokendesc代表单独一段词法
 */
struct tokendesc {
  /**
   * 词法所在位置
   * 该结构体比较简单 就不展开了 两个值代表起始、结束位置
   * 例如sample中 "'hello' + ' world'" 'hello'会被解析为token::string location为{0, 7}
   */
  location location = {0, 0};
  /**
   * 字符串词法相关
   */
  literalbuffer literal_chars;
  literalbuffer raw_literal_chars;
  /**
   * 词法的枚举类型
   * 例如 '(' 是 token::lparen '===' 是 token::eq_strict
   * 所有类型可见token.h
   */
  token::value token = token::uninitialized;
  messagetemplate invalid_template_escape_message = messagetemplate::knone;
  location invalid_template_escape_location;
  // 小整数
  uint32_t smi_value_ = 0;
  bool after_line_terminator = false;
}

  通过这个结构体和一些方法,就能完整的将源字符串逐步转换为抽象语法树。但是实际转换过程非常复杂,分支极多,后面再继续探究。