1. 插入分号
ECMAScript中,大部分声明和语句,都必须以分号结尾,
原则上,必须书写这些分号。
但是为了方便起见,在某些情况下,分号可以省略,
ECMAScript会使用自动插入分号机制(Automatic Semicolon Insertion),
将省略的分号插入到源代码中。
2. 自动分号插入规则
自动分号插入机制,包含以下三条规则,以及两个例外情况。
2.1 规则
(1)由于源代码是从左到右解析的,所以如果一个token不满足已有的任何文法(grammar),且满足以下条件,那么就在这个token前面插入一个分号。
这个token,通常被称为offending token。
a) 该offending token与它之前的token被至少一个LineTerminator分隔。
b) 该offending token是}
。
c) 该offending token前面的token是)
,且插入分号后,
之前的语句可以被解析为do-while,do Statement while (Expression) ;
。
(2)当读到token流结束标记的时候,如果整个token流无法解析成一个起始非终结符(goal nonterminal / goal symbol / start symbol),则会在末尾插入一个分号。
(3)即使一个token符合文法中的某个产生式,也可能会在它前面自动插入分号。
这种情况出现在受限产生式(restricted production)中,
受限产生式指的是包含[no LineTerminator here]
字样的产生式,例如,
ReturnStatement :
return;
return [no LineTerminator here] Expression ;
如果一个token出现在受限产生式中[no LineTerminator here]
的后面,且与前面的token被至少一个LineTerminator分隔,就会在该token前面插入分号。
这个token,通常被称为restricted token。
2.2 例外
在满足前三条规则的情况下,有两个例外。
(1)如果插入的分号,被解释为空语句,则不插入分号。
(2)由于for语句的头部包含两个分号,如果插入的分号会变成这两个分号中的任何一个,则不插入分号。
3. 所有的受限产生式
UpdateExpression :
LeftHandSideExpression [no LineTerminator here] ++
LeftHandSideExpression [no LineTerminator here] --
ContinueStatement :
continue ;
continue [no LineTerminator here] LabelIdentifier ;
BreakStatement :
break;
break [no LineTerminator here] LabelIdentifier ;
ReturnStatement :
return;
return [no LineTerminator here] Expression ;
ThrowStatement :
throw [no LineTerminator here] Expression ;
ArrowFunction :
ArrowParameters [no LineTerminator here] => ConciseBody
YieldExpression :
yield [no LineTerminator here] *AssignmentExpression
yield [no LineTerminator here] AssignmentExpression
在实际编程时,受限产生式对我们具有以下指导意义:
(1)如果++
或--
需要被作为后缀运算符解析的时候,它们前面就不能出现LineTerminator,否则++
和--
之前会被自动插入分号。
因此,++
和--
后缀运算符,应该与它们的操作数在同一行。
(2)如果continue
,break
,return
,throw
或yield
后面出现了LineTerminator,那么它们后面会被自动插入分号。
因此,return
,throw
和yield
,应该与后面的表达式在同一行,break
和continue
后面的label标签,应该和它们在同一行。
4. 例子
(1){ 1 2 } 3
不是一个合法的ECMAScript字符串,即使通过自动插入分号。
但是,下面字符串经过自动插入分号,就变成了合法的字符串,
{ 1
2 } 3
{ 1
;2 ;} 3;
(2)以下字符串不合法,且无法自动插入分号,
因为自动插入的分号,不能成为for语句头部中的那两个分号之一。
for (a; b
)
(3)以下字符串会通过自动插入分号,变成两条语句。
因此,a + b
并不会作为返回值被return
。
return
a + b
return;
a + b;
(4)以下字符串,会自动插入分号,使得++
变成前缀运算符。
a = b
++c
a = b;
++c;
(5)以下字符串不合法,不是因为自动插入分号后,其后的else c = d
不合法。
而是因为,自动插入的分号被解析成了空语句,因此就不能插入分号。
if (a > b)
else c = d
(6)以下字符串,不会自动插入分号,
因为第二行的(
,将被解析为函数调用的左括号。
a = b + c
(d + e).print()
a = b + c(d + e).print()
如果一个赋值语句必须以左括号开头(其他语句很少不得不以左括号开头),
最佳实践就是,显式的在该语句前的行尾加一个分号,而不是依赖自动分号插入机制。
参考
ECMAScript 2017 Language Specification
11.9 Automatic Semicolon Insertion
5.1.1 Context-Free Grammars
Wikipedia: Context-free grammar
Is "goal symbol" the same thing as "start symbol" in context-free-grammar
网友评论