Google V8 js解析流程-语法分析

1,886 阅读14分钟

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

.... 待续。