// 字符链
// 比如`name`这样一个Token,有4个字符,这4个字符就连成了一个LiteralScope
// 当扫描完这4个字符后,会调用`LiteralScope::Complete`,这样就完成了一个Token的分析
LiteralScope literal(this);
// 扫描unicode转义字符
if (c0_ == '\\') {
uc32 c = ScanIdentifierUnicodeEscape();
// Only allow legal identifier start characters.
if (c < 0 ||
c == '\\' || // No recursive escapes.
!unicode_cache_->IsIdentifierStart(c)) {
return Token::ILLEGAL;
}
// \u0020
AddLiteralChar(c);
return ScanIdentifierSuffix(&literal);
}
uc32 first_char = c0_;
Advance();
AddLiteralChar(first_char);
// 向前扫描字符,直至遇到一个不是有效的identifier字符为止,比如空格、(、[等
while (unicode_cache_->IsIdentifierPart(c0_)) {
if (c0_ != '\\') {
uc32 next_char = c0_;
Advance();
AddLiteralChar(next_char);
continue;
}
// Fallthrough if no longer able to complete keyword.
return ScanIdentifierSuffix(&literal);
}
// 结束一个字符链,即已经扫描出了一个identifier或者keyword
literal.Complete();
if (next_.literal_chars->is_one_byte()) {
Vector<const uint8_t> chars = next_.literal_chars->one_byte_literal();
// 判断是标志符还是关键词
// v8内部定义了一系列关键词matcher,能match到的就是关键词,否则就是标志符
return KeywordOrIdentifierToken(chars.start(),
chars.length(),
harmony_scoping_,
harmony_modules_);
}
return Token::IDENTIFIER;
我们以前文的buffer_字符数组为例,经过Scanner扫描后,会产生类似这样的结果:
最后,对整个文件内容扫描完的结果大概就类似这样:
No of tokens: 12
=> FUNCTION at (0, 8)
=> IDENTIFIER at (9, 11)
=> LPAREN at (11, 12)
=> RPAREN at (12, 13)
=> LBRACE at (14, 15)
=> VAR at (20, 23)
=> IDENTIFIER at (24, 26)
=> ASSIGN at (27, 28)
=> STRING at (29, 41)
=> SEMICOLON at (41, 42)
=> RBRACE at (43, 44)
=> EOS at (45, 45)
本文将探讨v8是如何对utf8编码的js文件进行词法分析的!
简介
v8提供了一个
Scanner
类作为词法分析器,在该类中有这么一个指针:源代码中对这个类的解释:
大概就是说,
Utf16CharacterStream
就是一个UTF-16代码单元的缓冲流。什么是代码单元?
这就涉及到了unicode规范,在unicode字符集中,每个unicode字符都有一个唯一的代码点。
在utf8、utf16等编码形式中,代码点被映射到一个或者多个代码单元。
代码单元是各个编码形式中的最小单元,其大小:
每个代码点需要多少个代码单元呢?不同编码形式不一样:
综上,我们可以知道
Utf16CharacterStream
中每一个UTF-16代码单元的含义:简而言之,
Utf16CharacterStream
中的每个代码单元,要么是一个独立的unicode字符,要么是一个unicode字符的一半(和下一个代码单元组合起来才能形成一个unicode字符)。v8实际上就是基于utf16对输入字符序列进行词法分析的!这也是Ecmascript规范所要求的:
v8词法分析首先要做的是把输入字符序列转换成utf16 stream。
那如何把utf8编码的js文件转换成utf16 stream呢?
字节流
首先,我们需要先获得utf8字节流:
假设文件内容如下:
utf8字节流如下:
转换
现在,我们需要把utf8的字节流转换成utf16字符流,前面我们说了,词法分析器扫描的是
Utf16CharacterStream
类,不过这个类是一个抽象类,不能直接使用, v8用了一个BufferedUtf16CharacterStream
类来提供具体的功能,它继承至Utf16CharacterStream
:这个
BufferedUtf16CharacterStream
提供了一个缓冲区buffer_
,我们转换的时候,就是要向这个缓冲区填充字符。具体的转换功能由一个叫
Utf8ToUtf16CharacterStream
的类提供,它继承至BufferedUtf16CharacterStream
:基本用法:
在生成一个
BufferedUtf16CharacterStream
实例之时,在其构造函数中,会进行第一次字符填充。在
Utf8ToUtf16CharacterStream::FillBuffer
方法中,有这么一段代码:最后的结果就是,
BufferedUtf16CharacterStream::buffer_
中的每一个元素都是一个16位代码单元,它要么是一个字符,要么是一个“Unicode代码对”的一半。我们以上文得到的字节流为例,最后
BufferedUtf16CharacterStream::buffer_
中会类似这样:得到所有字符之后,接下来的工作就是扫描每个字符,分析出
Token
。扫描
v8扫描
BufferedUtf16CharacterStream::buffer_
中的每个代码单元,然后按照Ecmascript的文法产生式分析出所有Token。我们先来看看
Scanner::Next
这个方法:我们注意到上面代码中的
one_char_tokens
,这货是啥?它其实就是个大小为128的数组,其中元素大部分是Token::ILLEGAL,只有几个有效的Token:如果没匹配到
one_char_tokens
,则调用Scanner::Scan
继续扫描:注意到上面的
ScanIdentifierOrKeyword
方法,这个方法是Token分析的核心,顾名思义,它就是用来分析identifier
或者keyword
的:我们以前文的
buffer_
字符数组为例,经过Scanner
扫描后,会产生类似这样的结果:最后,对整个文件内容扫描完的结果大概就类似这样:
在utf8编码的情况下,v8整个词法分析过程,大概就是这样,当然实际过程中,还有很多细节问题需要处理,比如跳过js注释、html注释、扫描十六进制字符、8进制字符、unicode转义字符等等。
这就是本人粗略看了v8词法分析源码之后的一点理解,欢迎各路大神点评!