JavaScript

[ECMAScript] 自动分号插入机制

2017-10-18  本文已影响20人  何幻

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-whiledo 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)如果continuebreakreturnthrowyield后面出现了LineTerminator,那么它们后面会被自动插入分号。
因此,returnthrowyield,应该与后面的表达式在同一行,breakcontinue后面的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

上一篇 下一篇

猜你喜欢

热点阅读