Acorn 的 Tokenizer 是解析器对象 Parser 上的一个静态方法,该方法可以根据输入的字符串和相关选项对输入进行处理得到 Token:
1 | export function tokenizer(input, options) { |
而 Parser.tokenizer
直接返回一个 Parser 对象,对象的原型上有两种方式得到 Token 序列,一种是调用 getToken
方法,该方法会返回下一个 Token,另一种是通过迭代器访问 ( 如果支持 ):
1 | // pp = Parser.prototype |
Token 的构造如下:
1 | export class Token { |
由此可知,Acorn 主要通过 next 方法解析输入字符串,并根据解析结果修改 Parser 上的状态 ( type
、value
、start
、end
等等 ),再根据这些数据构造 Token 并返回,所以 Token 主要就是通过这些更新的属性构造得到。
next
方法主要做两件事:
- 将上一个 Token 的偏移和位置记录下来。
- 调用
nextToken
读取下一个 Token。
1 | pp.next = function(ignoreEscapeSequenceInKeyword) { |
nextToken
读取下一个 Token 并更新 Parser 上和 Token 相关的数据,只有使用 new Token(this)
读取更新的数据生成 Token。
1 | pp.nextToken = function() { |
curContext
用来保存和当前上下文相关的属性,例如下面用到的 preserveSpace
,如果是在字符串的上下文中,空白会被保留,大多数情况下,空白 ( 包括注释 ) 会被跳过。
start
和 startLoc
分别设置即将读取的 Token 的起始 offset 和 location。
如果上下文中指定了用于覆盖默认 Token 解析方法的方法,则调用 curContext.override
,否则直接调用 readToken 读取下一个 Token,下面看 readToken
这条路径。
该方法先调用 fullCharCodeAtPos
得到当前位置 ( 即代码中的 pos
) 处的 Unicode 字符的编号。JavaScript 采用了 Unicode 字符集,同时 EMCAScript 规定了 JavaScript 源码被看做是 UTF-16 的码元序列 ( 但 JS 只能处理 UCS-2 编码 ) ,因此 charCodeAt
方法会返回指定位置的一个 2 字节 ( 16 bit ) 的码元。为了得到指定位置的 Unicode 字符编码,Acorn 自己进行转换 ( 在 ES6 下也可以用 codePointAt
)。
在 JS 中,对于 Unicode 的编码,如果编码在 U+0000 ~ U+FFFF 中,使用两个字节编码,如果在 U+100000 ~ U+10FFFF 使用四个字节编码,使用下面规则编码:
1 | function toUTF16(codePoint) { |
使用下面表格说明转换的具体情况 (每一个符号表示 1 或 0):
码点 | UTF-16 码元 |
---|---|
xxxxxxxxxxxxxxxx (16 bits) | xxxxxxxxxxxxxxxx |
pppppxxxxxxyyyyyyyyyy (21 bits = 5+6+10 bits) | 110110qqqqxxxxxx 110111yyyyyyyyyy (qqqq = ppppp − 1) |
对于 16 位以内的码点,UTF-16 码元和其码点值相等。对于 21 位以内的码点,首先减去 0x100000
,此时后 16 位不受影响 xxxxxxyyyyyyyyyy
,ppppp
则因此变为 qqqq
(少一位),接着将得到的 20 位分为两部分 qqqqxxxxxx
和 yyyyyyyyyy
分别加上前缀 110110
和 110111
得到 UTF-16 编码的两个两字节结果。
为了避免冲突,U+0000 ~ U+FFFF 中的 U+D800 ~ U+DFFF 部分为空,这样根据一个码元的取值就可以知道当前位置的码点是两个字节还是四个字节,可以将 UTF-16 编码还原为码点。Acorn 中的 fullCharCodeAtPos
就是做这件事,具体代码如下:
1 | pp.fullCharCodeAtPos = function() { |
下面说明 0x35fdc00
得到的方法:
1 | 0011 0110 qqqq xxxx xx # code << 10 |
得到当前的字符以后,使用 readToken
读取下一个 Token:
1 | pp.readToken = function(code) { |
下面是标准中说明的可行的 identifierStart,可以是指定范围内的 Unicode 字符、$
、_
和 以 \
开头的 Unicode 转义序列:
1 | IdentifierStart :: |
所以 readToken
先确认 code
是否是可行的 identifierStart,如果是接下来读取一个标识符,否则读取其他 Token。
参考资料
Chapter 24. Unicode and JavaScript - Speaking Javascript
Unicode与JavaScript详解