懒得发首页了,有时候因为贴的代码太多会被下,而且这东西本来也只是对自己学习的记录,阅读体验极差,所以就本地自娱自乐的写着吧!

由于是解析字符串,所以在开始之前介绍一下词法结构体中关于管理字符串类的属性。之前在TokenDesc中,有两个属性,如下。

/**
* 词法结构体
* 每一个TokenDesc代表单独一段词法
*/
struct TokenDesc {
/**
* 字符串词法相关
*/
LiteralBuffer literal_chars;
LiteralBuffer raw_literal_chars;
// ...
}

当时没有详细讲,主要也是比较麻烦,在这里介绍一下该类。

class LiteralBuffer final {
public:
/**
* 根据字符Unicode数值判断是单字节还是双字节字符
*/
void AddChar(uc32 code_unit) {
if (is_one_byte()) {
if (code_unit <= static_cast<uc32>(unibrow::Latin1::kMaxChar)) {
AddOneByteChar(static_cast<byte>(code_unit));
return;
}
ConvertToTwoByte();
}
AddTwoByteChar(code_unit);
}
private:
/**
* 配置
* constexpr int MB = KB * KB; constexpr int KB = 1024;
*/
static const int kInitialCapacity = ;
static const int kGrowthFactor = ;
static const int kMaxGrowth = * MB;
/**
* 向容器加字符
*/
void AddOneByteChar(byte one_byte_char) {
if (position_ >= backing_store_.length()) ExpandBuffer();
backing_store_[position_] = one_byte_char;
position_ += kOneByteSize;
}
/**
* 容器扩容
* 初始至少有64的容量 根据需要扩容
* 会生成一个新容量的vector 把数据复制过去并摧毁老的容器
*/
void LiteralBuffer::ExpandBuffer() {
int min_capacity = Max(kInitialCapacity, backing_store_.length());
Vector<byte> new_store = Vector<byte>::New(NewCapacity(min_capacity));
if (position_ > ) {
MemCopy(new_store.begin(), backing_store_.begin(), position_);
}
backing_store_.Dispose();
backing_store_ = new_store;
}
/**
* 扩容算法
* min_capacity代表容器最小所需容量
* (1024 * 1024) / 3 是一个阈值
* 小于该值容量以4倍的速度扩张 大于该值容量直接写死
*/
int LiteralBuffer::NewCapacity(int min_capacity) {
return min_capacity < (kMaxGrowth / (kGrowthFactor - ))
? min_capacity * kGrowthFactor
: min_capacity + kMaxGrowth;
}
/**
* Vector容器用来装字符
* potions_根据单/双字符类型影响length的计算
*/
Vector<byte> backing_store_;
int position_;
bool is_one_byte_;
};

其实原理非常简单,用一个Vector容器去装字符,如果容量不够,会进行扩张。

暂时不管双字节字符(比如中文),所以需要关注的属性和方法就是上面的那些,有一个地方可以关注一下,就是扩容。根据扩容机制,初始会有16 * 4的容量,当所需容量大到一定程度,会写死,这里来计算一下写死的最大容量。

/**
* 计算 kMaxGrowth = 1024 * 1024 = 1048576
* 得到阈值 (kMaxGrowth / (kGrowthFactor - 1) = 1048576 / (4 - 1) = 349525.333
* 而未达到阈值前容器容量会从16开始每次乘以4 如下
* 64 256 1024 4096 16384 65536 262144 1048576
* 当扩容第7次时才出现比阈值大的数 这个值恰好等于1mb 因此容器容量最大值就是2mb
*/

单个字符串的解析长度原来是有上限的,最大为2mb,长度约为200万,此时会向Vector容量外的下标赋值,不知道会出现什么情况。

回到上一篇的结尾,由于匹配到单引号,所以会走ScanString方法,源码如下。

Token::Value Scanner::ScanString() {
uc32 quote = c0_;
/**
* 初始化
*/
next().literal_chars.Start();
while (true) {
/**
* 对字符串的结尾预检测
*/
AdvanceUntil([this](uc32 c0) {
// ...
});
/**
* 遇到‘\’直接步进
* 后面如果直接是字符串结尾标识符 判定为非法
*/
while (c0_ == '\\') {
Advance();
if (V8_UNLIKELY(c0_ == kEndOfInput || !ScanEscape<false>())) {
return Token::ILLEGAL;
}
}
/**
* 又遇到了同一个字符串标识符
* 说明字符串解析完成
*/
if (c0_ == quote) {
Advance();
return Token::STRING;
} /**
* 没有合拢的字符串 返回非法标记
*/
if (V8_UNLIKELY(c0_ == kEndOfInput || unibrow::IsStringLiteralLineTerminator(c0_))) {
return Token::ILLEGAL;
}
// 向Vector里面塞一个字符
AddLiteralChar(c0_);
}
}

总的来说还是比较简单的,正常步进是初始化用过的Advance。代码中有一个方法叫AdvanceUntil,从函数名判断是一个预检函数。这个方法调用的结构非常奇怪,C++语法我也是TM日了狗,主要作用就是预先判断一下当前解析的字符串是否合法,整个函数结构如下。

/**
* 参数是一个匿名函数
*/
AdvanceUntil([this](uc32 c0) {
// Unicode大于127的特殊字符
if (V8_UNLIKELY(static_cast<uint32_t>(c0) > kMaxAscii)) {
/**
* 检测是否是换行符
* \r\n以及\n
*/
if (V8_UNLIKELY(unibrow::IsStringLiteralLineTerminator(c0))) {
return true;
}
AddLiteralChar(c0);
return false;
}
/**
* 检查是否是字符串结束符
*/
uint8_t char_flags = character_scan_flags[c0];
if (MayTerminateString(char_flags)) return true;
AddLiteralChar(c0);
return false;
}); /**
* 这个方法会对c0_进行赋值
*/
void AdvanceUntil(FunctionType check) {
c0_ = source_->AdvanceUntil(check);
} template <typename FunctionType>
V8_INLINE uc32 AdvanceUntil(FunctionType check) {
while (true) {
/**
* 从游标位置到结尾搜索符合条件的字符
*/
auto next_cursor_pos =
std::find_if(buffer_cursor_, buffer_end_, [&check](uint16_t raw_c0_) {
uc32 c0_ = static_cast<uc32>(raw_c0_);
return check(c0_);
});
/**
* 1、碰到第二个参数 说明没有符合条件的字符 直接返回结束符
* 2、有符合条件的字符 把游标属性指向该字符的后一位 返回该字符
*/
if (next_cursor_pos == buffer_end_) {
buffer_cursor_ = buffer_end_;
if (!ReadBlockChecked()) {
buffer_cursor_++;
return kEndOfInput;
}
} else {
buffer_cursor_ = next_cursor_pos + ;
return static_cast<uc32>(*next_cursor_pos);
}
}
}

这里的调用方式比较邪门,其实就是JS的高阶函数,函数作为参数传入函数,比较核心的就是find_if方法与函数参数,这里就不讲std的方法了,用JS翻译一下,不然看起来实在太痛苦。

const callback = (str) => IsStringLiteralLineTerminator(str);

const AdvanceUntil = (callback) => {
let tarArea = buffer_.slice(buffer_cursor_, buffer_end_);
let tarIdx = tarArea.findIdx(v => callback(v));
if(tarIdx === - 1) return '非法字符串';
buffer_cursor_ = tarIdx + 1;
c0_ = buffer_[tarIdx];
}

就是这么简单,变量直接对应,逻辑的话也就上面这些,find_if也就是根据索引来找符合对应条件的值。也就是说,唯一需要讲解的就是字符串结束符的判断。

涉及的新属性有两个,其中一个是映射数组character_scan_flags,另外一个是MayTerminateString方法,两者其实是一个东西,可以放一起看。

inline bool MayTerminateString(uint8_t scan_flags) {
return (scan_flags & static_cast<uint8_t>(ScanFlags::kStringTerminator));
} /**
* 字符扫描标记
*/
enum class ScanFlags : uint8_t {
kTerminatesLiteral = << ,
// "Cannot" rather than "can" so that this flag can be ORed together across
// multiple characters.
kCannotBeKeyword = << ,
kCannotBeKeywordStart = << ,
kStringTerminator = << ,
kIdentifierNeedsSlowPath = << ,
kMultilineCommentCharacterNeedsSlowPath = << ,
}; /**
* 映射表
* 对字符的可能性进行分类
*/
static constexpr const uint8_t character_scan_flags[] = {
#define CALL_GET_SCAN_FLAGS(N) GetScanFlags(N),
INT_0_TO_127_LIST(CALL_GET_SCAN_FLAGS)
#undef CALL_GET_SCAN_FLAGS
};

首先可以看出,character_scan_flags也是类似于之前那个Unicode与Ascii的表,对所有字符做一个映射,映射的值就是那个枚举类型,一个字符可能对应多个可能性。这里的计算方法可以参照我之前那篇利用枚举与位运算做配置,需要哪个属性,就用对应的枚举与字符映射值做与运算。

这个映射表的生成比较简单粗暴,会对每一个字符做6重或运算生成一个数,目前只看字符串终止符那块。

constexpr uint8_t GetScanFlags(char c) {
return
/** 1 */ | /** 2 */ | /** 3 */ |
// Possible string termination characters.
((c == '\'' || c == '"' || c == '\n' || c == '\r' || c == '\\')
? static_cast<uint8_t>(ScanFlags::kStringTerminator)
: ) | /** 5 */ | /** 6 */
}

也就是说,当前字符是单双引号、换行与反斜杠时,会被认定可能是一个字符串的结尾。

回到编译字符串'Hello',由于在字符结束之前,就存在另一个单引号,所以这个符号被认为可能是结束符号赋值给了c0_,Stream类的游标也直接移到了那个位置。至于中间的H、e、l、l、o5个字符,因为不存在任何特殊性,所以在最后的AddLiteralChar方法中被添加进了容器中。

结束后,整个函数正常返回Token::STRING作为词法结构体的类型,结构体的Literal_chars的容器则存储着对应的字符串。

深入V8引擎-AST(5)的更多相关文章

  1. 深入V8引擎-AST(1)

    没办法了,开坑吧,接下来的几篇会讲述JavaScript字符串源码在v8中转换成AST(抽象语法树)的过程. JS代码在V8的解析只有简单的几步,其中第一步就是将源字符串转换为抽象语法树,非常类似于v ...

  2. 深入V8引擎-AST(3)

    上篇简单介绍了入口方法的流程以及scanner类相关的部分内容,这一篇主要讲scanner的初始化,即 scanner_.Initialize(); 注意,这不是调用静态方法.实际上Parser实例生 ...

  3. 深入V8引擎-AST(6)

    花了5篇才把一个字符串词法给解析完,不知道要多久才能刷完整个流程,GC.复杂数据类型的V8实现那些估计又是几十篇,天呐,真是给自己挖了个大坑. 前面几篇实际上只是执行了scanner.Initiali ...

  4. 深入V8引擎-AST(2)

    先声明一下,这种长系列的大块头博客只能保证尽可能的深入到每一行源码,有些代码我不乐意深究就写个注释说明一下作用.另外,由于本地整理的比较好,博客就随心写了. 整个Compile过程目前只看到asmjs ...

  5. 深入V8引擎-AST(4)

    (再声明一下,为了简单暴力的讲解AST的转换过程,这里的编译内容以"'Hello' + ' World'"作为案例) 上一篇基本上花了一整篇讲完了scanner的Init方法,接下 ...

  6. [翻译] V8引擎的解析

    原文:Parsing in V8 explained 本文档介绍了 V8 引擎是如何解析 JavaScript 源代码的,以及我们将改进它的计划. 动机 我们有个解析器和一个更快的预解析器(~2x), ...

  7. 精读《V8 引擎 Lazy Parsing》

    1. 引言 本周精读的文章是 V8 引擎 Lazy Parsing,看看 V8 引擎为了优化性能,做了怎样的尝试吧! 这篇文章介绍的优化技术叫 preparser,是通过跳过不必要函数编译的方式优化性 ...

  8. Javascript的V8引擎研究

    1.针对上下文的Snapshot技术 什么是上下文(Contexts)?实际是JS应用程序的运行环境,避免应用程序的修改相互影响,例如一个页面js修改内置对象方法toString,不应该影响到另外页面 ...

  9. V8引擎——详解

    前言 JavaScript绝对是最火的编程语言之一,一直具有很大的用户群,随着在服务端的使用(NodeJs),更是爆发了极强的生命力.编程语言分为编译型语言和解释型语言两类,编译型语言在执行之前要先进 ...

随机推荐

  1. SQLServer修改表名、修改列名

    基本语法 修改表名:EXEC sp_rename ‘原有表名’, '新表名'; 修改列名:EXEC sp_rename ‘表名.[原有列名]’, ‘新列名' , 'COLUMN'; EXEC sp_r ...

  2. Java实现贪吃蛇游戏(含账号注册登录,排行榜功能)

    这是我第一次工程实践的作业,选题很多,但我只对其中的游戏开发感兴趣,可游戏就两三个类型,最终还是选择了贪吃蛇.其实就贪吃蛇本身的代码实现还算是比较简单的,可是实践要求代码行达到一定数量,所以我就额外给 ...

  3. 英语_金丝楠是紫楠(phoebeSheareri)的别名

    姚黄魏紫俱凋零--红木家具今古谈(连载七) [上海木业网]楠木品种包括闽楠.细叶楠.红毛山楠.滇楠.白楠.紫楠.乌心楠.桢楠.水楠.香楠等二百余种之多,1997年的木材国家标准中就列入了八种.某些售卖 ...

  4. 4 CVE-2012-0158 漏洞分析

    操作系统:Windows7 32位 专业版 Office:2003sp3_20120218.exe 工具:OD和IDA 1.漏洞的本质:程序编写时未对内存拷贝函数的长度参数进行足够严谨的验证,造成的堆 ...

  5. Hadoop 从节点的 NodeManager 无法启动

    一.问题描述 日志文件信息如下: -- ::, INFO nodemanager.NodeManager (LogAdapter.java:info()) - registered UNIX sign ...

  6. jquery实现checkbox列表的全选不选

    html代码 <th><input type="checkbox" onclick="selectAll(this);" />全选/取消 ...

  7. 01-MySQL 大纲介绍

    MySQL 大纲介绍 1.官方定义的MySQL DBA工作内容 (1)运维DBA 初级:各版本.各平台安装搭建.升级 中级:体系结构原理.基础管理(启动关闭.初始化配置文件管理.多实例管理.用户权限管 ...

  8. 003-OpenStack-镜像服务

    OpenStack-镜像服务 [基于此文章的环境]点我快速打开文章 1.安装和配置 控制节点(controller) 1.1 创库授权 glance mysql CREATE DATABASE gla ...

  9. node知识

    node中的url url中的方法: parse,resolve,format: 方法parse: 例子:url.parse('http://imooc.com/course/list'); 结果:{ ...

  10. 基本 Python 面试问题

    目录 1.为什么学习Python? 2.通过什么途径学习的Python? 3.Python和Java.PHP.C.C#.C++等其他语言的对比? 4.简述解释型和编译型编程语言? 5.Python解释 ...