java用正则表达式 编写简单词法分析器_从底层看前端(九)—— JavaScript词法...

409d5b22b3b0bb914ab96c35c587e772.png

在前面的几篇文章中,我们已经从运行时的角度了解了JavaScript的内容,在接下来的几篇文章中,我们来了解一下JavaScript的文法部分。

文法是编译原理中对语言的写法的一种规定,一般来说,文法分成词法和语法两种。

词法规定了语言的最小语义单元:token,可以翻译成'标记'或者'词',在我的专栏文章中,统一把 token 翻译成词。

从字符到词的整个过程是没有结构的,只要符合词的规则,就构成词,一般来说,词法设计不会包含冲突。词法分析技术上可以使用状态机或者正则表达式来进行。

我们先来看一看JavaScript的词法定义。JavaScript源代码中的输入可以这样分类:

WhiteSpace 空白字符
LineTerminator 换行符
Comment 注释
Tiken 词

其中Token又分以下几类:

IdentifierName 标识符名称,典型例子就是我们使用的变量名,注意这里关键字也包含在里了。
Punctuator 符号,我们使用的运算符和大括号等符号。
NumbericLiteral 数字直接量,就是我们写的数字。
StringLiteral 字符串直接量,就是我们用单引号或者双引号引起来的直接量。
Template 字符串模板,用反括号`括起来的直接量。

这个设计符合比较通用的编程语言设计方式,不过,JavaScript中有一些特别之处。

首先是除法和正则表达式冲突问题。我们知道,JavaScript不但支持除法运算符 '/' 和 '/=',还支持用斜杠括起来的正则表达式 '/abc/'。

但是,这时候对词法分析来说,其实是没有办法处理的,所以JavaScript的解决方案是定义两组词法,然后靠语法分析传一个标志给词法分析器,让它来决定使用哪一套词法。

JavaScript词法的另一个特别设计之处是字符串模板,模板语法大概是这样的:

`Hello 

理论上,"${}" 内部可以放任何JavaScript表达式代码,而这些代码是以 "}" 结尾的,也就是说,这部分词法不允许出现 "}" 运算符。

是否允许 "}" 的两种情况,与除法和正则表达式的两种情况相乘就是四种词法定义,所以在JavaScript标准中,可以看到四种定义:

InputElementDiv;
InputElementRegExp;
InputElementRegExpOrTemplateTail;
InputElementTemplatetail;

为了解决这两个问题,标准中还不得不把除法,正则表达式直接量和 '}' 从 token 中单独抽出来,用词上,也把原本的 token 改为了 CommonToken。

但是我认为,从理解的角度来说,我们不应该受到影响,所以在本文中,我们依旧把它归类到 token 来理解。

对于一般语言的词法分析过程来说,都会丢弃除了 token 之外的输入,但是对JavaScript来说,不太一样,换行符和注释还会影响语法分析过程,这个我们将会在后续的文章中继续探讨。

所以,要实现JavaScript的解释器,词法分析和语法分析非常麻烦,需要来回传递信息。

下面我们来分别介绍下几种 token。

空白符 Whitespace

说起空白符,想必给大家留下印象的就是空格。但是实际上,JavaScript可以支持更多的空白符号。

<HT> 也称<TAB> U+0009,也就是tab缩进符号,字符串中的t。
<VT> U+000B c垂直方向的缩进符号 v。
<FF> U+000C Form Feed 分页符 在JavaScript中用的极少。
<sp> U+0020 就是最普通的空格。
<NBSP> U+00A0 非断行空格,它是SP的一个变体,在文字排版中可以避免因为空格在此处发生断行。
<ZWNBSP> U+FEFF,ES5新加入的空白符,是Unicode中的零宽非断行空格。

此外,JavaScript还支持很多 Unicode 中空格分类下的空格,具体的可以找相关资料看看。

需要提到的是,很多公司的编码规范要求JavaScript源代码控制在ASCII范围内,那么,就只有<TAB>,<VT>,<FF>,<SP>,<NBSP>这五种可用了。

换行符 LineTerminator

接下来我们来看看换行符,JavaScript中只提供了四种字符作为换行符。

<LF> U+000A,就是最正常的换行符,在字符串中是n。
<CR> U+000d,这个字符是真正意义上的"回车",在字符串中是r,在一部分Windows风格文本编辑器中,换行是两个字符 rn。
<LS> U+2028,是Unicode中的行分隔符。
<PS> U+2029,是Unicode中的段落分割符。

大部分的 LineTerminator 在被词法分析器扫描出之后,会被语法分析器丢弃,但是换行符会影响JavaScript的两个重要语法特性:自动插入分号和 'no line terminator' 规则。

注释 Comment

JavaScript的注释分为单行注释和多行注释两种。

/* MultiLineCommentChars */ 

多行注释中允许自由地出现 MultiLineCommentChars,也就是除了 * 之外的所有字符,而每一个 * 之后,不能出现正斜杠符号/。

除了四种 Lineterinator 之外,所有字符都可以作为单行注释。

我们需要注意,多行注释中是否包含换行符,会对JavaScript语法产生影响,对于 no line terminator 规则来说,带换行的多行注释与换行符是等效的。

标识符名称 IndentifierName

IndentifierName 可以以美元符号‘$’,下划线 ‘_’ 或者Unicode字母开始,除了开始字符以外,IndentifierName 中还可以使用Unicode中的连接标记,数字,以及连接符号。

IndentifierName 的任意字符可以使用JavaScript的Unicode转义写法,使用Unicode转义写法时,没有任何字符限制。

IndentifierName 可以是 Identifier,Nullliteral,BooleanLiteral 或者 Keyword,在objectLiteral 中,IndentifierName 还可以被直接当成属性名称来使用。

只有当不是保留字的时候,IndentifierName 才会被解析成 Identifier。

前面有提到,关键字也属于这个部分,在JavaScript中,关键字有:

await

此外,还有一个为了未来使用而保留的关键字

enum

在严格模式下,有一些额外的为未来使用保留的关键字:

implements

除了这些之外,NullLiteral(null)和BooleanLiteral(true false)也是保留字,不能用于Identifier

符号 Punctuator

因为前面提到的除法和正则问题,/ 和 /= 两个运算符被拆分成 DivPunctuator,因为前面提到的字符串模板问题,} 也被独立拆分。加在一起,所有符号如下:

{ 

数字直接量 NumbericLiteral

JavaScript规范中规定的数字直接量可以支持四种写法:十进制数,二进制整数,八进制整数和十六进制整数。

十进制的 Number 可以带小数,小数点前后面的部分都可以省略,但是不能同时省略。

有这样一个著名的问题:

12.toString()为什么会报错?

这里的12.会被当成省略了小数点后面部分的数字而看成一个整体,所以我们想让点单独成为一个token,就要加入空格,这样写:

12 

就不会报错了。

数字直接量还支持科学计数法

10.24E+2

这里e后面的部分,只允许使用整数,以0x 0b或者0o开头时,表示特定进制的整数。

0xFA

字符串直接量 StringLiteral

JavaScript中的字符串直接量支持单引号和双引号两种写法。

" DoubleStringCharacters "
    

单引号和双引号的区别仅仅在于写法,在单引号字符串直接量中,双引号需要转义,反之亦然。

字符串中和换行符都必须转义。

正则表达式直接量 RegularExpressionLiteral

正则表达式由Body和Flags两部分组成,例如

/RegularExpressionBody/g

其中 Body 部分至少有一个字符,第一个字符不能是 *(因为 /* 跟多行注释有词法冲突)

正则表达式有自己的语法规则,在词法阶段,仅会对它做简单解析。

正则表达式并非机械地见到 / 就停止,在正则表达式[]中的 / 会被认为是普通字符。

例如

/[/]/

除了 ,/ 和 [ 这三个字符外,JavaScript正则表达式中的字符都是普通字符。

字符串模板Template

从语法结构上来说,Template 是个整体,其中 ${} 是并列关系。

但是实际上,在JavaScript词法中,包含了 ${} 的Template,是被拆开分析的:

`a

它在JavaScript中被认为是:

`a

它被拆成了五个部分:

`a{ 模板头
}c${ 模板中段
}e` 模板尾
b和d都是普通标识符

实际上,这里的词法分析过程已经跟语法分析深度耦合了。

不过我们在学习的时候,不需要按照标准和引擎工程师这样去理解,这样太复杂了。可以简单认为模板就是一个由反括号括起来的,可以在中间插入代码的字符串。

模板也支持添加处理函数的写法,这是模板的各段会被拆开,传递个函数当参数。

function 
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值