先声明一下, 这种长系列的大块头博客只能保证尽可能的深入到每一行源码, 有些代码我不乐意深究就写个注释说明一下作用. 另外, 由于本地整理的比较好, 博客就随心写了.
整个 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;
- }
通过这个结构体和一些方法, 就能完整的将源字符串逐步转换为抽象语法树. 但是实际转换过程非常复杂, 分支极多, 后面再继续探究.
来源: https://www.cnblogs.com/QH-Jimmy/p/11115324.html