上篇简单介绍了入口方法的流程以及scanner类相关的部分内容,这一篇主要讲scanner的初始化,即

scanner_.Initialize();

注意,这不是调用静态方法。实际上Parser实例生成的时候也把scanner属性初始化了,所以这里可以直接用。

Parser::Parser(ParseInfo* info) : ParserBase<Parser>(/* 初始化父类的属性 */)
scanner_(info->character_stream(), info->is_module()),/* 初始化其他属性 */

实际上,就是初始化了scanner上的source_属性与模块的flag,以便调用Initialize方法。

这个方法有点类似于libuv的异步操作,不过当然完全不是一个东西,源码如下。

/**
* 注意 这里不做AST的全面转换
*/
void Scanner::Initialize() {
Init();
next().after_line_terminator = true;
Scan();
}

第二步我也不晓得是干啥的,暂时不理解那个变量的意义,所以只讲第一和第三步,首先是Init。

void Init() {
Advance();
/**
* TokenDesc token_storage_[3];
* 这里做一个映射 相当于alias
*/
current_ = &token_storage_[];
next_ = &token_storage_[];
next_next_ = &token_storage_[]; found_html_comment_ = false;
scanner_error_ = MessageTemplate::kNone;
} /**
* source_在Parser的构造函数中初始化
* 类型为Utf16CharacterStream 需要去那边看实现
*/
void Advance() {
c0_ = source_->Advance();
}

从scanner层级来看,其Advance方法的作用仅仅是对私有属性c0_(当前字符的Unicode编码)进行赋值,做实际操作是source_属性上的Advance方法,而这个属性类型为前面转换后的Stream类(全称是xxxCharacterStream,因为太长了,后面全部简称Stream类),所以具体实现需要跳到那边去,源码如下。

/**
* 从这里开始方法域跳到了Utf16CharacterStream、BufferedCharacterStreams
* 即Utf16CharacterStream::Advance、Utf16CharacterStream::Peek、Utf16CharacterStream::ReadBlockChecked
*/
inline uc32 Advance() {
uc32 result = Peek();
buffer_cursor_++;
return result;
} /**
* 返回游标所在位置的值
* 1、已初始化
* 2、未初始化
* 3、已到结尾
*/
inline uc32 Peek() {
if (V8_LIKELY(buffer_cursor_ < buffer_end_)) {
return static_cast<uc32>(*buffer_cursor_);
} else if (ReadBlockChecked()) {
return static_cast<uc32>(*buffer_cursor_);
} else {
return kEndOfInput;
}
}

这里有一些东西需要解释,首先是关于Stream类的3个游标属性(这个名字是我自己取的,看AST的解析总让我想到高中的游标卡尺),分别是buffer_start_、buffer_cursor_、buffer_end_,分别代表字符解析中的开始、当前、结束位置,在Stream类初始化时这三个属性没有处理,默认置0。注意,这里的属性指向字符,跟词法是不同的概念,在scanner层级的三个属性是词法。比如说if从词法角度讲是一个,但是从字符角度来说是两个。

下面的3个判断注释中给出了意义,比较有意思的是V8_LIKELY宏,对于开发者来说算是一个无意义的宏,但是这个宏是给编译器看的,表明这个分支比较有可能发生,推荐进行优化。由于初始化只会走一遍,在解析未结束前大部分情况都是走第一个分支直接返回当前游标指向的值。不过目前是第一次调用这个方法,我们走第二个分支。

/**
* 这里是做一个合法性检测
* 实际上只有ReadBlock做事
*/
bool ReadBlockChecked() {
size_t position = pos();
USE(position);
bool success = !has_parser_error() && ReadBlock(); // Post-conditions: 1, We should always be at the right position.
// 2, Cursor should be inside the buffer.
// 3, We should have more characters available iff success.
DCHECK_EQ(pos(), position);
DCHECK_LE(buffer_cursor_, buffer_end_);
DCHECK_LE(buffer_start_, buffer_cursor_);
DCHECK_EQ(success, buffer_cursor_ < buffer_end_);
return success;
} /**
* buffer_pos_代表当前进度位置 类型为整形
* cursor、start作为指针指向buffer_数组的当前、初始地址
* 而数组在内存中地址连续 且unsigned short类型占1
* 所以可以直接通过计算得到当前位置
*/
inline size_t pos() const {
return buffer_pos_ + (buffer_cursor_ - buffer_start_);
} /**
* 1、buffer_是一个unsigned short数组 存储编码处理后的单个字符
* 2、指针start、end分别初始化为数组的头尾
* 3、cursor是游标 初始指向start
* 例如"(function)"在buffer_表示为[40, 102, ...]
*/
bool ReadBlock() final {
size_t position = pos();
buffer_pos_ = position;
buffer_start_ = &buffer_[];
buffer_cursor_ = buffer_start_; DisallowHeapAllocation no_gc;
Range<uint8_t> range = byte_stream_.GetDataAt(position, runtime_call_stats(), &no_gc);
if (range.length() == ) {
buffer_end_ = buffer_start_;
return false;
} size_t length = Min(kBufferSize, range.length());
i::CopyCharsUnsigned(buffer_, range.start, length);
buffer_end_ = &buffer_[length];
return true;
}

这一块的内容较多,实际上说多也不多。第一个方法只是纯粹的检查,保证游标属性的合法,pos方法则是直接通过地址计算来得到当前解析位置,原理写在注释里了。

ReadBlock方法负责对Stream属性的初始化,这个类前面没有给出声明,buffer_是其一个私有属性,长度为512的short数组。DisallowHeapAllocation不要去管,v8里面有很多奇奇怪怪的东西,目前理解不了,当然与AST本身也毫无关系。GetDataAt比较麻烦,不想讲,从结果上来讲,最后返回的是字符串每个字符的Unicode编码,通过CopyCharsUnsigned方法复制到了buffer_上面,并将buffer_end_指向了最后结尾的部分。

比如说待编译字符串为"'Hello' + ' World'",经过GetDataAt处理后,会变成39, 72, ...。

这里给一个调试结果,buffer_初始化后,会有一堆脏数据,内容如下(长度512,只截取了前面一部分)。

经过该方法的一系列处理,变成了

加上空格,整个字符串共有18位,所以0-17的值全部被重置,后面还是老的脏数据。这些数字手动转换一下,可以得到

刚好是待编译的字符串(先假设字符串长度小于512,复杂情况后面再搞)。

至此,整个Init方法才完事,没想到这么长,Scan下一篇讲,要干活了。

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

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

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

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

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

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

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

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

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

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

    懒得发首页了,有时候因为贴的代码太多会被下,而且这东西本来也只是对自己学习的记录,阅读体验极差,所以就本地自娱自乐的写着吧! 由于是解析字符串,所以在开始之前介绍一下词法结构体中关于管理字符串类的属性 ...

  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. 窗体的基类中没有设定大小,所以才不能居中,若要窗体居中,必须使用setfixedsize()函数或者resize()函数设定窗体的大小,居中才能正常使用

    最近开发中,遇到了窗体不能居中的问题,看了网上的很多文章,窗口居中,无非都是move至窗口的中心目标; 有两种方式, 一种在构造函数中直接计算中心坐标; 另一种是在窗口show后再move至相应坐标. ...

  2. 对XAML进行编辑的辅助类(XamlHelper)

    原文:对XAML进行编辑的辅助类(XamlHelper) // XamlHelper.cs// --------------------------------------------// 对XAML ...

  3. java_Mac安装多个JDK版本并设置环境变量

    JDK6.JDK7.JDK8三个版本的汇总地址:https://pan.baidu.com/s/1pKSYv8Z 安装 JDK6安装 JDK6下载地址 见网盘 安装完毕后确认版本 JavaForOSX ...

  4. Android Intent传递对象摘要

    效果: watermark/2/text/aHR0cDovL2Jsb2cuY3Nkbi5uZXQvaG9uZ3NoZW5ncGVuZw==/font/5a6L5L2T/fontsize/400/fil ...

  5. typescript Json Convert

    关键代码 this.data={}; // json string this.dataStr=JSON.stringify(this.data); // json object this.conver ...

  6. QRCode二维码生成方案及其在带LOGO型二维码中的应用(2)

    原文:QRCode二维码生成方案及其在带LOGO型二维码中的应用(2) 续前:QRCode二维码生成方案及其在带LOGO型二维码中的应用(1)  http://blog.csdn.net/johnsu ...

  7. html 自制属性

    HTML5 允许扩展的(自制的)属性,以 data- 开头 <label id="id0" data-value="0">0</label&g ...

  8. Linux C lock pages

      虚拟内存按页划分,我们可以明确告诉系统:某一个虚拟内存页需要和实际内存帧相关联.这样一来,该内存页就被换进来了,而且不会被系统换出去.这一行为叫做锁页(locking a page). 一般来讲页 ...

  9. Lexer的设计--上(3)

    lexer的构造函数 有了上一节Token做铺垫, 可以开始设计lexer, 首先应该想到的是, 源代码是以文件流的格式传到编译器中的, 所以作为编译器的前段的第一个阶段, lexer必须负责处理输入 ...

  10. c# 全局钩子实现扫码枪获取信息。

    原文:c# 全局钩子实现扫码枪获取信息. 1.扫描枪获取数据原理基本相当于键盘数据,获取扫描枪扫描出来的数据,一般分为两种实现方式. a)文本框输入获取焦点,扫描后自动显示在文本框内. b)使用键盘钩 ...