最近正在研究JS动态解析的基本结构,希望自己能够将研究成果前前后后总结出来。
以方便自己复习,也希望能够和大家分享这样一套拥有悠久历史的编译技术实现。
按照编译原理的运行特点,一套解释系统最前面也是最简单的就是词法分析。
这里首先研究的是V8引擎的词法分析结构,头文件定义在 src/scanner.h中,具体实现在对应的src/scanner.cc中。
所有的构件
- Scanner [v8::internal]
- UC16CharacterStream [v8::internal]
- UnicodeCache [v8::internal]
- LiteralBuffer [v8::internal]
- LiteralScope [v8::internal::Scanner]
在头文件中共声明有五个类,但是对外提供词法分析服务的就是Scanner类。其他四个类为词法分析提供基本基础服务:譬如字符流编码、结果存取、输入流缓冲等。
UC16CharacterStream [v8::internal]
对外主要提供三个接口:
- Advance [v8::internal::UC16CharacterStream] 向前扫描一个字符,并返回扫描到的字符,如果到文件尾部则返回一个负数。
- pos [v8::internal::UC16CharacterStream] 返回当前字符流扫描到的位置
- SeekForward [v8::internal::UC16CharacterStream] 向前N步步进
- PushBack [v8::internal::UC16CharacterStream] 回溯一个字符
其是一个抽象类,与其相关的子类如图:
UnicodeCache [v8::internal]
用于对Unicode字符进行类型分析。其内部专门实现了用于支持Unicode的相关类包。主要是便于词法分析器在扫描到当前字符时可以进行当前字符状态的查询。
其内部含有各种类型分辨数据单元:
- unibrow::Predicate<IdentifierStart, 128> kIsIdentifierStart;
- unibrow::Predicate<IdentifierPart, 128> kIsIdentifierPart;
- unibrow::Predicate<unibrow::LineTerminator, 128> kIsLineTerminator;
- unibrow::Predicate<unibrow::WhiteSpace, 128> kIsWhiteSpace;
例如IndentifierStart数据题用于判断是否为起始字符:
struct IdentifierStart {
static inline bool Is(uc32 c) {
switch (c) {
case '$': case '_': case '\\': return true;
default: return unibrow::Letter::Is(c);
}
}
};
LiteralBuffer [v8::internal]
当前分析结果的字符流存储缓存结构。
内部用 Vector<byte> backing_store_ 保存扫描的字符流数据。
LiteralScope [v8::internal::Scanner]
用于记录在完成词法扫描后的词法分析状态
Scanner [v8::internal]
词法分析主体类,原先感觉是否会使用lex等自动生成器,没想到时完全手写版本。
字符流的基本分析方法时DFA,如果有时间,我考虑把分析器的DFA逆向画出来。
Scanner内部主要维护了current_,next_两步的token,具体的维护策略需要看语法分析的维护。