Google V8 js解析流程
parser.cc ,parser-base.h ,ast.h 主要负责解析js词法,生成对应的节点。
compiler.cc 用来串起来整套流程。先让parser解析,再让interpreter.cc 生成字节码。
编译流程
进入编译
// v8.h
static V8_WARN_UNUSED_RESULT MaybeLocal<Script> Compile(
Local<Context> context, Local<String> source,
ScriptOrigin* origin = nullptr);
当 js 传入之后,此处作为 source 参数传递给 Script::Compile 方法。此方法的实现在 api.cc 中。
// api.cc
MaybeLocal<Script> Script::Compile(Local<Context> context, Local<String> source,
ScriptOrigin* origin) {
if (origin) {
ScriptCompiler::Source script_source(source, *origin);
return ScriptCompiler::Compile(context, &script_source);
}
ScriptCompiler::Source script_source(source);
return ScriptCompiler::Compile(context, &script_source);
}
最后调用的是4个参数的 Compile 方法,后面两个参数都是默认参数。CompileOptions options = kNoCompileOptions, NoCacheReason no_cache_reason = kNoCacheNoReason。所以实际调用的是下面的四个参数的 Compile 方法:
// api.cc
MaybeLocal<Script> ScriptCompiler::Compile(Local<Context> context,
Source* source,
CompileOptions options,
NoCacheReason no_cache_reason) {
......
auto maybe =
CompileUnboundInternal(isolate, source, options, no_cache_reason); // <1>
Local<UnboundScript> result;
if (!maybe.ToLocal(&result)) return MaybeLocal<Script>();
v8::Context::Scope scope(context);
return result->BindToCurrentContext();
}
看代码,掐头去尾,去掉看上去就是检查的代码,去掉打点相关的代码,看到其中最重要的一行<1>.
// api.cc
MaybeLocal<UnboundScript> ScriptCompiler::CompileUnboundInternal(
Isolate* v8_isolate, Source* source, CompileOptions options,
NoCacheReason no_cache_reason) {
......
i::Handle<i::String> str = Utils::OpenHandle(*(source->source_string));
i::Handle<i::SharedFunctionInfo> result;
......
i::Compiler::ScriptDetails script_details = GetScriptDetails(
isolate, source->resource_name, source->resource_line_offset,
source->resource_column_offset, source->source_map_url,
source->host_defined_options);
i::MaybeHandle<i::SharedFunctionInfo> maybe_function_info =
i::Compiler::GetSharedFunctionInfoForScript(
isolate, str, script_details, source->resource_options, nullptr,
script_data, options, no_cache_reason, i::NOT_NATIVES_CODE); // <1>
.......
}
最重要的就是<1>处,用来 js 代码的共享方法信息。 GetScriptDetails 是在构造 js 脚本的详情,生成ScriptDetails 作为 GetSharedFunctionInfoForScript 方法的参数。省略的地方都是一些参数生成处理和校验。
// compiler.cc
// script_details: GetScriptDetails方法构造出来的。
// origin_options: ScriptOrigin.options_
// extension: nullptr
// cached_data: nullptr
// compile_options: kNoCompileOptions
// no_cache_reason: kNoCacheNoReason
// natives: i::NOT_NATIVES_CODE
MaybeHandle<SharedFunctionInfo> Compiler::GetSharedFunctionInfoForScript(
Isolate* isolate, Handle<String> source,
const Compiler::ScriptDetails& script_details,
ScriptOriginOptions origin_options, v8::Extension* extension,
ScriptData* cached_data, ScriptCompiler::CompileOptions compile_options,
ScriptCompiler::NoCacheReason no_cache_reason, NativesFlag natives) {
........
int source_length = source->length();
isolate->counters()->total_load_size()->Increment(source_length); //增加数据统计。
isolate->counters()->total_compile_size()->Increment(source_length);
LanguageMode language_mode = construct_language_mode(FLAG_use_strict); // 使用严格模式
CompilationCache* compilation_cache = isolate->compilation_cache(); // 获取编译缓存。
// Do a lookup in the compilation cache but not for extensions.
MaybeHandle<SharedFunctionInfo> maybe_result;
IsCompiledScope is_compiled_scope;
if (extension == nullptr) {
.....
// 查询缓存
// First check per-isolate compilation cache.
maybe_result = compilation_cache->LookupScript(
source, script_details.name_obj, script_details.line_offset,
script_details.column_offset, origin_options, isolate->native_context(),
language_mode); // <1>
if (!maybe_result.is_null()) {
compile_timer.set_hit_isolate_cache(); //标记命中缓存。
} else if (can_consume_code_cache) { // 也无法进入。
.......
}
}
// 没有命中任何缓存
if (maybe_result.is_null()) {
ParseInfo parse_info(isolate);
// No cache entry found compile the script.
NewScript(isolate, &parse_info, source, script_details, origin_options,
natives); //创建 Handle<Script>
// Compile the function and add it to the isolate cache.
if (origin_options.IsModule()) parse_info.set_module(); // 条件假
parse_info.set_extension(extension); // 传入null
parse_info.set_eager(compile_options == ScriptCompiler::kEagerCompile); // 设置 false
parse_info.set_language_mode(
stricter_language_mode(parse_info.language_mode(), language_mode)); // 严格模式
maybe_result = CompileToplevel(&parse_info, isolate, &is_compiled_scope); // 当作顶层代码编译。<2>
Handle<SharedFunctionInfo> result;
if (extension == nullptr && maybe_result.ToHandle(&result)) {
DCHECK(is_compiled_scope.is_compiled());
compilation_cache->PutScript(source, isolate->native_context(),
language_mode, result); // 放入缓存
} else if (maybe_result.is_null() && natives != EXTENSION_CODE) {
isolate->ReportPendingMessages();
}
}
return maybe_result;
}
通过代码中的注释,可以大致了解其中的逻辑。先进行一系列的参数设置,模式判断,然后先去缓存中查找是否编译过,如果有则直接返回,如果没有则继续进行下一步,当作顶层代码来进行编译,如果编译成功,通过标识判断是否可以加入缓存。CompilationCache 是缓存相关的逻辑,暂时先不提。上段代码中比较重要的就是<1>,<2> 两处。<1>是去查询缓存,<2> 是去从头进行编译。
那么就话分两头。
<1>查询缓存
// compliation-cache.cc
// name,line_offset,column_offset: 都来自 script_details 变量的成员。
// origin_options: ScriptOrigin.options_
// native_context: isolate->native_context()
// language_mode: strict
MaybeHandle<SharedFunctionInfo> CompilationCache::LookupScript(
Handle<String> source, MaybeHandle<Object> name, int line_offset,
int column_offset, ScriptOriginOptions resource_options,
Handle<Context> native_context, LanguageMode language_mode) {
if (!IsEnabled()) return MaybeHandle<SharedFunctionInfo>(); // 仅一个开关控制。
return script_.Lookup(source, name, line_offset, column_offset,
resource_options, native_context, language_mode);
}
// 参数同上。
MaybeHandle<SharedFunctionInfo> CompilationCacheScript::Lookup(
Handle<String> source, MaybeHandle<Object> name, int line_offset,
int column_offset, ScriptOriginOptions resource_options,
Handle<Context> native_context, LanguageMode language_mode) {
MaybeHandle<SharedFunctionInfo> result;
// Probe the script generation tables. Make sure not to leak handles
// into the caller's handle scope.
// 从缓存表中进行缓存的查询。使用 scope 避免内存泄漏。
{
HandleScope scope(isolate()); // handles.h local handles 的控制器,批量处理,避免内存泄漏。
const int generation = 0;
DCHECK_EQ(generations(), 1);
Handle<CompilationCacheTable> table = GetTable(generation); // 根据年代级别来获取查询表。<1>
MaybeHandle<SharedFunctionInfo> probe = CompilationCacheTable::LookupScript(
table, source, native_context, language_mode); // 查询对应脚本的缓存。<2>
Handle<SharedFunctionInfo> function_info;
if (probe.ToHandle(&function_info)) {
// Break when we've found a suitable shared function info that
// matches the origin.
if (HasOrigin(function_info, name, line_offset, column_offset,
resource_options)) {
result = scope.CloseAndEscape(function_info);
}
}
}
...... // 处理打点标记等操作。
return result;
}
说明几个点:
- HandleScope 是用来批量管理 local handle的,其具体行为类似将所有的 Handle 写在 { } 之间,等出了中括号(也就是 HandleScope 被销毁的时候),就自动进行销毁。具体原理可见 HandleScope 分析。
<1.1>GetTable
// compliation-cache.cc
Handle<CompilationCacheTable> CompilationSubCache::GetTable(int generation) {
.......
Handle<CompilationCacheTable> result;
if (tables_[generation].IsUndefined(isolate())) {
result = CompilationCacheTable::New(isolate(), kInitialCacheSize);
tables_[generation] = *result;
} else {
CompilationCacheTable table =
CompilationCacheTable::cast(tables_[generation]);
result = Handle<CompilationCacheTable>(table, isolate());
}
return result;
}
此处简单进行分析。可以看到先去判断了 tables_ 数组里面是否有对应的内容,如果没有就新建填充,如果有,则进行 else 的逻辑。直接进行一个转换,返回 CompilationCacheTable 这个类型的 Handle。
<1.2>lookupScript
// objects.cc
// table 上面 GetTable 的结果。
// language_mode: strict
MaybeHandle<SharedFunctionInfo> CompilationCacheTable::LookupScript(
Handle<CompilationCacheTable> table, Handle<String> src,
Handle<Context> native_context, LanguageMode language_mode) {
// We use the empty function SFI as part of the key. Although the
// empty_function is native context dependent, the SFI is de-duped on
// snapshot builds by the PartialSnapshotCache, and so this does not prevent
// reuse of scripts in the compilation cache across native contexts.
Handle<SharedFunctionInfo> shared(native_context->empty_function().shared(),
native_context->GetIsolate());
Isolate* isolate = native_context->GetIsolate();
src = String::Flatten(isolate, src);
StringSharedKey key(src, shared, language_mode, kNoSourcePosition); // 构造key
int entry = table->FindEntry(isolate, &key); // 从表中通过key查找值。
if (entry == kNotFound) return MaybeHandle<SharedFunctionInfo>();
int index = EntryToIndex(entry); // 获取索引
if (!table->get(index).IsFixedArray()) { //不是固定数组,就返回空。
return MaybeHandle<SharedFunctionInfo>();
}
Object obj = table->get(index + 1);
if (obj.IsSharedFunctionInfo()) { // 如果有值, 就返回缓存。
return handle(SharedFunctionInfo::cast(obj), native_context->GetIsolate());
}
return MaybeHandle<SharedFunctionInfo>();
}
整个过程简单来看,就是去缓存的 hash 表里面查找对应的缓存。hash表的结构此处也暂时不讲。
<2>从头编译
// complier.cc
// IsCompiledScope
MaybeHandle<SharedFunctionInfo> CompileToplevel(
ParseInfo* parse_info, Isolate* isolate,
IsCompiledScope* is_compiled_scope) {
.......
if (parse_info->literal() == nullptr &&
!parsing::ParseProgram(parse_info, isolate)) { // 进行解析。 <1>
return MaybeHandle<SharedFunctionInfo>(); // 解析失败返回空。
}
........
// 生成未优化的字节码。
MaybeHandle<SharedFunctionInfo> shared_info =
GenerateUnoptimizedCodeForToplevel(
isolate, parse_info, isolate->allocator(), is_compiled_scope); // <2>
if (shared_info.is_null()) {
FailWithPendingException(isolate, parse_info,
Compiler::ClearExceptionFlag::KEEP_EXCEPTION); // 出错报告。
return MaybeHandle<SharedFunctionInfo>();
}
FinalizeScriptCompilation(isolate, parse_info); // 回收编译过程占用的内存。
return shared_info;
}
先进行编译<1>,再生成字节码<2>。
<2.1>ParseProgram
// parsing.cc
//
bool ParseProgram(ParseInfo* info, Isolate* isolate,
ReportErrorsAndStatisticsMode mode) {
......
// Create a character stream for the parser.
Handle<String> source(String::cast(info->script()->source()), isolate);
isolate->counters()->total_parse_size()->Increment(source->length());
std::unique_ptr<Utf16CharacterStream> stream(
ScannerStream::For(isolate, source));
info->set_character_stream(std::move(stream));
Parser parser(info); // 构建字节流,方便进行解析。
FunctionLiteral* result = nullptr;
......
result = parser.ParseProgram(isolate, info); // 进行解析。
info->set_literal(result);
.......
return (result != nullptr);
}
创建了 stream 流,开始进行解析。
// parser.cc
//
FunctionLiteral* Parser::ParseProgram(Isolate* isolate, ParseInfo* info) {
......
// Initialize parser state.
DeserializeScopeChain(isolate, info, info->maybe_outer_scope_info(),
Scope::DeserializationMode::kIncludingVariables); // 初始化作用域。
scanner_.Initialize(); // 扫描器初始化。 <1>
scanner_.SkipHashBang(); // 跳过 #! 的注释。
FunctionLiteral* result = DoParseProgram(isolate, info); //开始扫描 <2>
......
return result;
}
这里关注扫描器初始化和开始扫描两部分。
<2.1.1> scanner_.Initialize
// scanner.cc
void Scanner::Initialize() {
Init(); // 初始化 c0_
next().after_line_terminator = true;
Scan(); // 生成第一个 token
}
void Init() {
// Set c0_ (one character ahead)
......
Advance(); // 赋值 c0_
current_ = &token_storage_[0];
next_ = &token_storage_[1];
next_next_ = &token_storage_[2]; // 为了回溯,保留了三个 token。
.......
}
// Low-level scanning support.
template <bool capture_raw = false>
void Advance() {
.......
c0_ = source_->Advance(); // c0_ 赋值为源码流的开始位置。
}
// 获取顶端的字符,并将指针后移一位。类似于栈的 pop 操作。
inline uc32 Advance() {
uc32 result = Peek();
buffer_cursor_++;
return result;
}
// 获取顶端的字符。
inline uc32 Peek() {
......
return static_cast<uc32>(*buffer_cursor_); //返回指针指向的字符。
}
初始化过程,首先是赋值 c0_, 就是预读一个字符。然后进行一次token解析。
// scanner-inl.h
void Scanner::Scan() { Scan(next_);
void Scanner::Scan(TokenDesc* next_desc) {
......
next_desc->token = ScanSingleToken();
......
next_desc->location.end_pos = source_pos();
......
}
这里扫描生成的是 next_ 这个变量,也就是 token_storage_[1]. 生成了 token之后,同时标明了结束位置, end_pos。来看看 ScanSingleToken 方法吧,方法比较长。
// scanner-inl.h
V8_INLINE Token::Value Scanner::ScanSingleToken() {
Token::Value token;
do {
next().location.beg_pos = source_pos();
if (V8_LIKELY(static_cast<unsigned>(c0_) <= kMaxAscii)) { // c0_ 如果是 ascii,即 0x00-0x7f 之间。
token = one_char_tokens[c0_]; //one_char_tokens 是一个128位的数组,其中处理了符号以及数字,字符等。直接查表,速度飞快。
switch (token) {
case Token::LPAREN:// 这些定义可以自行查阅 scanner-inl.h 的 GetOneCharToken 方法进行了解。
case Token::RPAREN: // ')'
case Token::LBRACE: // '{'
case Token::RBRACE: // .........
case Token::LBRACK:
case Token::RBRACK:
case Token::COLON:
case Token::SEMICOLON:
case Token::COMMA:
case Token::BIT_NOT:
case Token::ILLEGAL:
// One character tokens.
return Select(token); // 上面的属于一个字符的 token,直接进行返回。
case Token::CONDITIONAL:
// ? ?. ??
Advance();
if (V8_UNLIKELY(allow_harmony_optional_chaining() && c0_ == '.')) {
Advance();
if (!IsDecimalDigit(c0_)) return Token::QUESTION_PERIOD;
PushBack('.');
} else if (V8_UNLIKELY(allow_harmony_nullish() && c0_ == '?')) {
return Select(Token::NULLISH);
}
return Token::CONDITIONAL;
case Token::STRING:
return ScanString();
case Token::LT:
// < <= << <<= <!--
Advance();
if (c0_ == '=') return Select(Token::LTE);
if (c0_ == '<') return Select('=', Token::ASSIGN_SHL, Token::SHL);
if (c0_ == '!') {
token = ScanHtmlComment();
continue;
}
return Token::LT;
case Token::GT:
// > >= >> >>= >>> >>>=
Advance();
if (c0_ == '=') return Select(Token::GTE);
if (c0_ == '>') {
// >> >>= >>> >>>=
Advance();
if (c0_ == '=') return Select(Token::ASSIGN_SAR);
if (c0_ == '>') return Select('=', Token::ASSIGN_SHR, Token::SHR);
return Token::SAR;
}
return Token::GT;
case Token::ASSIGN:
// = == === =>
Advance();
if (c0_ == '=') return Select('=', Token::EQ_STRICT, Token::EQ);
if (c0_ == '>') return Select(Token::ARROW);
return Token::ASSIGN;
case Token::NOT:
// ! != !==
Advance();
if (c0_ == '=') return Select('=', Token::NE_STRICT, Token::NE);
return Token::NOT;
case Token::ADD:
// + ++ +=
Advance();
if (c0_ == '+') return Select(Token::INC);
if (c0_ == '=') return Select(Token::ASSIGN_ADD);
return Token::ADD;
case Token::SUB:
// - -- --> -=
Advance();
if (c0_ == '-') {
Advance();
if (c0_ == '>' && next().after_line_terminator) {
// For compatibility with SpiderMonkey, we skip lines that
// start with an HTML comment end '-->'.
token = SkipSingleHTMLComment();
continue;
}
return Token::DEC;
}
if (c0_ == '=') return Select(Token::ASSIGN_SUB);
return Token::SUB;
case Token::MUL:
// * *=
Advance();
if (c0_ == '*') return Select('=', Token::ASSIGN_EXP, Token::EXP);
if (c0_ == '=') return Select(Token::ASSIGN_MUL);
return Token::MUL;
case Token::MOD:
// % %=
return Select('=', Token::ASSIGN_MOD, Token::MOD);
case Token::DIV:
// / // /* /=
Advance();
if (c0_ == '/') {
uc32 c = Peek();
if (c == '#' || c == '@') {
Advance();
Advance();
token = SkipSourceURLComment();
continue;
}
token = SkipSingleLineComment();
continue;
}
if (c0_ == '*') {
token = SkipMultiLineComment();
continue;
}
if (c0_ == '=') return Select(Token::ASSIGN_DIV);
return Token::DIV;
case Token::BIT_AND:
// & && &=
Advance();
if (c0_ == '&') return Select(Token::AND);
if (c0_ == '=') return Select(Token::ASSIGN_BIT_AND);
return Token::BIT_AND;
case Token::BIT_OR:
// | || |=
Advance();
if (c0_ == '|') return Select(Token::OR);
if (c0_ == '=') return Select(Token::ASSIGN_BIT_OR);
return Token::BIT_OR;
case Token::BIT_XOR:
// ^ ^=
return Select('=', Token::ASSIGN_BIT_XOR, Token::BIT_XOR);
case Token::PERIOD:
// . Number
Advance();
if (IsDecimalDigit(c0_)) return ScanNumber(true);
if (c0_ == '.') {
if (Peek() == '.') {
Advance();
Advance();
return Token::ELLIPSIS;
}
}
return Token::PERIOD;
case Token::TEMPLATE_SPAN:
Advance();
return ScanTemplateSpan();
case Token::PRIVATE_NAME:
return ScanPrivateName();
case Token::WHITESPACE:
token = SkipWhiteSpace();
continue;
case Token::NUMBER:
return ScanNumber(false);
case Token::IDENTIFIER:
return ScanIdentifierOrKeyword();
default:
UNREACHABLE();
}
}
if (IsIdentifierStart(c0_) ||
(CombineSurrogatePair() && IsIdentifierStart(c0_))) {
return ScanIdentifierOrKeyword();
}
if (c0_ == kEndOfInput) {
return source_->has_parser_error() ? Token::ILLEGAL : Token::EOS;
}
token = SkipWhiteSpace();
// Continue scanning for tokens as long as we're just skipping whitespace.
} while (token == Token::WHITESPACE);
return token;
}
// 就是字符流向前走一位,并返回传入的 token。
inline Token::Value Select(Token::Value tok) {
Advance();
return tok;
}
解析逻辑比较整齐,通过一个大的 switch,进行了分支判断,简单的 token 直接进行了返回,而如 <=, !== 这种多元的运算符,需要解析多个token才能真正确定真正的token。我们看一下 ADD 的处理:
case Token::ADD:
// + ++ +=
Advance();
if (c0_ == '+') return Select(Token::INC);
if (c0_ == '=') return Select(Token::ASSIGN_ADD);
return Token::ADD;
注释也比较清晰,当解析到一个 + 号时,他可能是+ ,也可能是 ++ ,也可能是 += 。所以再判断一个字符,下一个字符如果是 + ,就是自增++,如果是=,就是+=,如果都不是,那么就是+。其他的解析以此类推。我们可能也要感叹,原来大家都是要写一大堆的 if else 的😊。 其中字符串的解析,稍微复杂一些,有兴趣的可以看看 ScanString();
到此,初始化的逻辑就算走完了。
<2.1.2> DoParseProgram
// parser.cc
//
FunctionLiteral* Parser::DoParseProgram(Isolate* isolate, ParseInfo* info) {
......
FunctionLiteral* result = nullptr;
{
........
ParseStatementList(&body, Token::EOS);
.......
}
.......
RecordFunctionLiteralSourceRange(result);
return result;
}
去掉其中暂时走不到的分支和打点相关的逻辑,主要的逻辑就在 ParseStatementList 方法中了。
template <typename Impl>
typename ParserBase<Impl>::StatementT
ParserBase<Impl>::ParseStatementListItem() {
// ECMA 262 6th Edition
// StatementListItem[Yield, Return] :
// Statement[?Yield, ?Return]
// Declaration[?Yield]
//
// Declaration[Yield] :
// HoistableDeclaration[?Yield]
// ClassDeclaration[?Yield]
// LexicalDeclaration[In, ?Yield]
//
// HoistableDeclaration[Yield, Default] :
// FunctionDeclaration[?Yield, ?Default]
// GeneratorDeclaration[?Yield, ?Default]
//
// LexicalDeclaration[In, Yield] :
// LetOrConst BindingList[?In, ?Yield] ;
switch (peek()) {
case Token::FUNCTION:
return ParseHoistableDeclaration(nullptr, false);
case Token::CLASS:
Consume(Token::CLASS);
return ParseClassDeclaration(nullptr, false);
case Token::VAR:
case Token::CONST:
return ParseVariableStatement(kStatementListItem, nullptr);
case Token::LET:
if (IsNextLetKeyword()) {
return ParseVariableStatement(kStatementListItem, nullptr);
}
break;
case Token::ASYNC:
if (PeekAhead() == Token::FUNCTION &&
!scanner()->HasLineTerminatorAfterNext()) {
Consume(Token::ASYNC);
return ParseAsyncFunctionDeclaration(nullptr, false);
}
break;
default:
break;
} // 以上都是在解析 Declaration。
// 如果不属于 Declaration,则进行 Statement 解析。
return ParseStatement(nullptr, nullptr, kAllowLabelledFunctionStatement);
}
根据编译原理来看,v8的解析也是递归下降分析的方法。其中定义的语义主要是看代码开头的那部分注释。可以暂时忽略掉 yield,这些字符。结构如下:
StatementListItem
Statement
Declaration
| HoistableDeclaration
| FunctionDeclaration
| GeneratorDeclaration
| ClassDeclaration
| LexicalDeclaration
| LetOrConst BindingList
我们j看一个分支, case: Token:VAR 的代码。
// parser-base.h
template <typename Impl>
typename ParserBase<Impl>::StatementT ParserBase<Impl>::ParseVariableStatement(
VariableDeclarationContext var_context,
ZonePtrList<const AstRawString>* names) {
// VariableStatement ::
// VariableDeclarations ';'
DeclarationParsingResult parsing_result;
ParseVariableDeclarations(var_context, &parsing_result, names);
ExpectSemicolon();
return impl()->BuildInitializationBlock(&parsing_result);
}
template <typename Impl>
void ParserBase<Impl>::ParseVariableDeclarations(
VariableDeclarationContext var_context,
DeclarationParsingResult* parsing_result,
ZonePtrList<const AstRawString>* names) {
// VariableDeclarations ::
// ('var' | 'const' | 'let') (Identifier ('=' AssignmentExpression)?)+[',']
//
// ES6:
// FIXME(marja, nikolaos): Add an up-to-date comment about ES6 variable
// declaration syntax.
........
switch (peek()) {
case Token::VAR:
parsing_result->descriptor.mode = VariableMode::kVar;
Consume(Token::VAR);
break;
case Token::CONST:
Consume(Token::CONST);
DCHECK_NE(var_context, kStatement);
parsing_result->descriptor.mode = VariableMode::kConst;
break;
case Token::LET:
Consume(Token::LET);
DCHECK_NE(var_context, kStatement);
parsing_result->descriptor.mode = VariableMode::kLet;
break;
default:
UNREACHABLE(); // by current callers
break;
}
.......
do {
.......
IdentifierT name;
ExpressionT pattern;
// Check for an identifier first, so that we can elide the pattern in cases
// where there is no initializer (and so no proxy needs to be created).
if (V8_LIKELY(Token::IsAnyIdentifier(peek()))) {
name = ParseAndClassifyIdentifier(Next()); // 解析声明的名字。
.......
if (peek() == Token::ASSIGN ||
(var_context == kForStatement && PeekInOrOf()) || // 如果是赋值,或者 for in || for of 循环。
parsing_result->descriptor.mode == VariableMode::kLet) {
// Assignments need the variable expression for the assignment LHS, and
// for of/in will need it later, so create the expression now.
pattern = impl()->ExpressionFromIdentifier(name, decl_pos); // 生成用于 for of/in 的 pattern。
} else {
// Otherwise, elide the variable expression and just declare it.
impl()->DeclareIdentifier(name, decl_pos); // 否则就是简单的声明。
pattern = impl()->NullExpression();
}
} else {
name = impl()->NullIdentifier(); // 没有声明名字
pattern = ParseBindingPattern(); // 解析 数组或者对象来进行参数绑定,es6的自动绑定语法。类似 var [a, b] = [11, 22]; a==11, b == 22;
......
}
Scanner::Location variable_loc = scanner()->location();
ExpressionT value = impl()->NullExpression();
int value_beg_pos = kNoSourcePosition;
if (Check(Token::ASSIGN)) { // 如果是赋值
DCHECK(!impl()->IsNull(pattern));
{
value_beg_pos = peek_position();
AcceptINScope scope(this, var_context != kForStatement);
value = ParseAssignmentExpression(); //解析赋值表达式。
}
.......
impl()->SetFunctionNameFromIdentifierRef(value, pattern); // 设置方法名称。
} else {
......
}
......
} while (Check(Token::COMMA));
.......
}
可以看到仅仅是解析一个变量,其分支就非常多。do while 循环的条件是 token 是逗号,这表示可能存在多个变量一起声明。每次循环中,先解析变量名,而变量名又有多种情况,可能是类声明,可能是变量声明,可能是for循环,可能是自动解构。解析完变量名之后,还要解析赋值语句 ParseAssignmentExpression。
// parser-base.h
template <typename Impl>
typename ParserBase<Impl>::ExpressionT
ParserBase<Impl>::ParseAssignmentExpression() {
ExpressionParsingScope expression_scope(impl());
ExpressionT result = ParseAssignmentExpressionCoverGrammar();
expression_scope.ValidateExpression();
return result;
}
// Precedence = 2
template <typename Impl>
typename ParserBase<Impl>::ExpressionT
ParserBase<Impl>::ParseAssignmentExpressionCoverGrammar() {
// AssignmentExpression ::
// ConditionalExpression 比如三目操作符
// ArrowFunction 箭头函数。
// YieldExpression yield 表达式。
// LeftHandSideExpression AssignmentOperator AssignmentExpression
int lhs_beg_pos = peek_position();
if (peek() == Token::YIELD && is_generator()) { // 如果是 generate 函数。
return ParseYieldExpression(); // 解析 yield的函数。
}
......
ExpressionT expression = ParseConditionalExpression();
Token::Value op = peek();
if (!Token::IsArrowOrAssignmentOp(op)) return expression;
// Arrow functions.
.... //后面都是箭头函数的处理,不看也罢。代码比较多,仿佛看到了一个大写的 南南南南南南
}
// Precedence = 3
template <typename Impl>
typename ParserBase<Impl>::ExpressionT
ParserBase<Impl>::ParseConditionalExpression() {
// ConditionalExpression ::
// LogicalExpression
// LogicalExpression '?' AssignmentExpression ':' AssignmentExpression
//
int pos = peek_position();
ExpressionT expression = ParseLogicalExpression();
return peek() == Token::CONDITIONAL // 表示:'?'
? ParseConditionalContinuation(expression, pos) // 接着解析 ? 号后面的内容。
: expression;
}
template <typename Impl>
typename ParserBase<Impl>::ExpressionT
ParserBase<Impl>::ParseLogicalExpression() {
// LogicalExpression ::
// LogicalORExpression
// CoalesceExpression
// Both LogicalORExpression and CoalesceExpression start with BitwiseOR.
// Parse for binary expressions >= 6 (BitwiseOR);
ExpressionT expression = ParseBinaryExpression(6);
if (peek() == Token::AND || peek() == Token::OR) {
// LogicalORExpression, pickup parsing where we left off.
int prec1 = Token::Precedence(peek(), accept_IN_);
expression = ParseBinaryContinuation(expression, 4, prec1);
}
......
return expression;
}
// Precedence >= 4
template <typename Impl>
typename ParserBase<Impl>::ExpressionT ParserBase<Impl>::ParseBinaryExpression(
int prec) {
DCHECK_GE(prec, 4);
ExpressionT x = ParseUnaryExpression();
int prec1 = Token::Precedence(peek(), accept_IN_);
if (prec1 >= prec) {
return ParseBinaryContinuation(x, prec, prec1);
}
return x;
}
template <typename Impl>
typename ParserBase<Impl>::ExpressionT
ParserBase<Impl>::ParseUnaryExpression() {
// UnaryExpression ::
// PostfixExpression
// 'delete' UnaryExpression
// 'void' UnaryExpression
// 'typeof' UnaryExpression
// '++' UnaryExpression
// '--' UnaryExpression
// '+' UnaryExpression
// '-' UnaryExpression
// '~' UnaryExpression
// '!' UnaryExpression
// [+Await] AwaitExpression[?Yield]
Token::Value op = peek();
if (Token::IsUnaryOrCountOp(op)) return ParseUnaryOrPrefixExpression(); // 解析一元运算符。
return ParsePostfixExpression(); // 解析后部分。
}
template <typename Impl>
typename ParserBase<Impl>::ExpressionT
ParserBase<Impl>::ParseUnaryOrPrefixExpression() {
Token::Value op = Next();
int pos = position();
// 一个优化,如果是写成 !function 的样子,大概率是马上就要调用的,所以设置一下 set_next_function_is_likely_called.
if (op == Token::NOT && peek() == Token::FUNCTION) {
function_state_->set_next_function_is_likely_called();
}
CheckStackOverflow(); // 检查栈是否溢出
int expression_position = peek_position();
ExpressionT expression = ParseUnaryExpression(); // 解析一个一元表达式。
if (Token::IsUnaryOp(op)) {
........
// Allow the parser's implementation to rewrite the expression.
return impl()->BuildUnaryExpression(expression, op, pos); // 构造一元运算符并返回。
}
......
return factory()->NewCountOperation(op, true /* prefix */, expression,
position());
}
template <typename Impl>
typename ParserBase<Impl>::ExpressionT
ParserBase<Impl>::ParsePostfixExpression() {
// PostfixExpression ::
// LeftHandSideExpression ('++' | '--')?
int lhs_beg_pos = peek_position();
ExpressionT expression = ParseLeftHandSideExpression(); // 解析 属性等等操作。此处就不再深入了。
if (V8_LIKELY(!Token::IsCountOp(peek()) ||
scanner()->HasLineTerminatorBeforeNext())) {
return expression;
}
return ParsePostfixContinuation(expression, lhs_beg_pos);
}
可以看到,解析过程非常的繁杂,但是逻辑也比较清晰,主要是情况太多了。一步步抽丝剥茧,可以看到解析到达了 BuildUnaryExpression 这个方法上。
// parser.cc
Expression* Parser::BuildUnaryExpression(Expression* expression,
Token::Value op, int pos) {
DCHECK_NOT_NULL(expression);
const Literal* literal = expression->AsLiteral(); // 转成字面量。
if (literal != nullptr) {
if (op == Token::NOT) {
// Convert the literal to a boolean condition and negate it.
return factory()->NewBooleanLiteral(literal->ToBooleanIsFalse(), pos);
} else if (literal->IsNumberLiteral()) {
// Compute some expressions involving only number literals.
double value = literal->AsNumber();
switch (op) {
case Token::ADD:
return expression;
case Token::SUB:
return factory()->NewNumberLiteral(-value, pos);
case Token::BIT_NOT:
return factory()->NewNumberLiteral(~DoubleToInt32(value), pos);
default:
break;
}
}
}
return factory()->NewUnaryOperation(op, expression, pos);
}
通过通过判断类型,生成不同种类的 Expression 对象。上层不管如何处理,到最后都会到达这种最简单的逻辑上来,语法糖越多,解析过程就越复杂。
至此,解析就先暂时告一段落,大家大致对其应该有了一个浅显的了解了。其运用的就是递归下降分析方法,一层层进行处理。其中还夹在着一些优化,作用域处理,缓存配置等等。这些之后会再做一次分析。
而生成未优化的字节码的过程,留到下一节再讲。
<2.2> GenerateUnoptimizedCodeForToplevel
.... 待续。