理解ECMAScript规范(3)

发表于 3年以前  | 总阅读数:216 次

翻译本文的目的是尝试给出 ECMAScript 规范中核心术语的译法,供同好品评。

这一次我们深入 ECMAScript 语言及其语法的定义。如果你不太熟悉上下文无关文法[1],应该先补补课,至少先弄懂一些基本概念。因为规范中使用了上下文无关文法定义语言。

ECMAScript 文法

ECMAScript 规范定义了 4 种文法。

  1. 词法文法[2]:描述怎么把Unicode 码点[3](code point)翻译为输入元素(标记、行终止符、注释、空白)序列。
  2. 语法文法[4]:定义标记(token)怎么构成语法正确的程序。
  3. 正则文法[5]:描述怎么把 Unicode 码点翻译为正则表达式。
  4. 数值字符串文法[6]:描述怎么把 String 翻译成数字值。

每种文法都用上下文无关文法来定义,都包含一组产生式。

不同的文法使用了不同的表示方式。语法文法表示为LeftHandSideSymbol :,词法文法和正则文法表示为LeftHandSideSymbol ::,而数值字符串文法表示为LeftHandSideSymbol :::。(以冒号的多少来区分。——译者注)

接下来我们详细分析一下词法文法和语法文法。

词法文法

规范将 ECMAScript 源文本定义为一个 Unicode 码点序列。这意味着变量名并不限于 ASCII 字符,也可以包含其他 Unicode 字符。规范并没有谈到实际的编码(如 UTF-8 或 UTF-16),而是假设源代码已经按照自己的编码转换成了 Unicode 码点序列。

无法提前对 ECMAScript 源码进行标记化(tokenize),这使得定义词法文法略显复杂。比如,如果不看它所处的更大的上下文,就无法确定/是除法操作符还是正则表达式的开始。

const x = 10 / 5;

这里的/是DivPunctuator

const r = /foo/;

这里/是RegularExpressionLiteral的开头。

模板也引入了类似的歧义:对`}``的解释取决于它出现的上下文:

const what1 = 'temp';
const what2 = 'late';
const t = `I am a ${ what1 + what2 }`;

这里I am a ${TemplateHead,而`}``是TemplateTail。

if (0 == 1) {
}`not very useful`;

这里}RightBracePunctuator,而``NoSubstitutionTemplate`的开头。

即便对/}的解释取决于上下文(它们在代码语法结构中的位置),我们下面介绍的文法仍然是上下文无关的。

词汇文法使用一些目标符号(goal symbol)来区分哪些上下文允许哪些输入元素,不允许哪些输入元素。例如,目标符号InputElementDiv(注意,这里的Div是 Divide,即除法的意思。——译者注)会用在/是除法和/=是除法赋值的上下文中。InputElementDiv产生式列出了在此上下文中可能产生的标记:

InputElementDiv ::
  WhiteSpace
  LineTerminator
  Comment
  CommonToken
  DivPunctuator
  RightBracePunctuator

在这个上下文中,遇到/产生输入元素DivPunctuator,而不会产生RegularExpressionLiteral

相应地,对于/是正则表达式开头的上下文,目标符号是InputElementRegExp

InputElementRegExp ::
  WhiteSpace
  LineTerminator
  Comment
  CommonToken
  RightBracePunctuator
  RegularExpressionLiteral

这个产生式可以产生RegularExpressionLiteral输入元素,但不可能产生DivPunctuator

类似地,目标符号InputElementRegExpOrTemplateTail对应的上下文除了RegularExpressionLiteral,还允许出现TemplateMiddleTemplateTail。最后一个InputElementTemplateTail目标符号的上下文只允许TemplateMiddleTemplateTail,不允许出现RegularExpressionLiteral

在实现中,语法文法分析器(“解析器”)可以调用词法文法分析器(“标记器”或“词法器”),将目标符号作为参数传递,并请求适合该目标符号的下一个输入元素。

语法文法

词法文法定义了如何从 Unicode 码点构建标记。语法文法建立在它的基础上,定义了标记如何组成语法正确的程序。

例子:允许遗留标识符

给文法增加新关键字有可能造成破坏:如果原有代码已经使用该关键字作为标识符了怎么办?

例如,在await还不是关键字的时候,可能出现这样的代码:

function old() {
  var await;
}

ECMAScript 文法谨慎地添加了 await 关键字,以便这段代码可以继续工作。在 async 函数里面,await 是一个关键字,所以不能这样写:

async function modern() {
  var await; // 语法错误
}

在非生成器中允许yield,而在生成器中不允许与此类似。

要理解怎么允许await作为标识符,需要理解 ECMAScript 特定的语法文法表示。

产生式与简写形式

来看看VariableStatement的产生式是怎么定义的。乍一看,这个文法有点吓人:

VariableStatement[Yield, Await] :
 var VariableDeclarationList[+In, ?Yield, ?Await] ;

这里的下标([Yield, Await])和前缀(+In里的+?Await里的?)都什么意思呀?

这种表示法在“文法表示法[7]”中有解释。

下标是对一组产生式的简写形式,一次性表达了一组产生式左端(left-hand side)符号。这个产生式左端符号有两个参数,它们可以展开为四个“真正的”产生式左端符号:

  • VariableStatement
  • VariableStatement_Yield
  • VariableStatement_Await
  • VariableStatement_Yield_Await

注意,以上VariableStatement就表示VariableStatement,没有_Await也没有_Yield。不要把它跟<em>VariableStatement</em><sub style="line-height: 0;">[Yield, Await]</sub>(简写形式)弄混了。

在产生式右端,可以看到简写+In,意思是“使用带_In的版本”,而?Await的意思是“当且仅当左端符号有_Await时使用带_Await的版本”(?Yield也是类似的)。

第三种简写形式~Foo,意思是“使用没有_Foo的版本”(这个产生式中没有出现)。

了解了这些,可以把上面的产生式展开成这样:

 VariableStatement :
   var VariableDeclarationList_In ;

 VariableStatement_Yield :
   var VariableDeclarationList_In_Yield ;

 VariableStatement_Await :
   var VariableDeclarationList_In_Await ;

 VariableStatement_Yield_Await :
   var VariableDeclarationList_In_Yield_Await ;

最后,还有要搞清楚两件事。

  1. 在哪里确定我们处在有_Await或没有_Await的情况下?
  2. 有它和没它有什么区别,或者说Something_Await的产生式和Something(没有_Await)的产生式是在哪里分叉的?

_Await还是没有_Await

先解决第一个问题。因为很容易猜到可以根据函数体是否带_Await来区分异步函数和非异步函数。看异步函数声明的产生式,我们发现了这个[8]:

AsyncFunctionBody :
  FunctionBody[~Yield, +Await] 

注意AsyncFunctionBody没有参数,参数在右端被添加给了FunctionBody。展开这个产生式得到:

AsyncFunctionBody :
  FunctionBody_Await

换句话说,异步函数有FunctionBody_Await(即一个await会被当成关键字的)函数体。

另一方面,如果是在非异步函数中,相关产生式[9]为:

FunctionDeclaration[Yield, Await, Default] :
  function BindingIdentifier[?Yield, ?Await] ( FormalParameters[~Yield, ~Await]) { FunctionBody[~Yield, ~Await] }

FunctionDeclaration还有一个产生式,但跟我们的代码示例不相关。)

为避免组合展开,我们忽略Default参数,因为这个特别的产生式中没用到。于是,这个产生式的展开形式为:

FunctionDeclaration :
  function BindingIdentifier ( FormalParameters ) { FunctionBody } 

FunctionDeclaration_Yield :
  function BindingIdentifier_Yield ( FormalParameters ) { FunctionBody } 

FunctionDeclaration_Await :
  function BindingIdentifier_Await ( FormalParameters ) { FunctionBody } 

FunctionDeclaration_Yield_Await :
  function BindingIdentifier_Yield_Await ( FormalParameters ) { FunctionBody } 

在这个产生式中,只有FunctionBodyFormalParameters(不带_Yield,也不带_Await),因为在未展开的产生式中它们都有参数[~Yield, ~Await]

函数名的处理方式就不一样了:如果产生式左端符号中包含_Yield_Await,则右端的函数名也会带上相应参数。

总结:异步函数有FunctionBody_Await,而非异步函数有FunctionBody(不带_Await)。因为我们讨论的是非生成器函数,所以无论异步示例函数还是非异步示例函数,都不会带参数_Yield

可能记住哪个是FunctionBody,哪个是FunctionBody_Await有点难。在有FunctionBody_Await的函数体中,await是标识符,还是关键字?

可以把_Await参数的意思理解为“await是一个关键字。”这样理解在将来也不会有问题。假设将来又添加了一个blob关键字,但只适用于“斑驳”(blobby)函数。非斑驳、非异步、非生成器的函数仍然有FunctionBody(不带_Yield_Await_Blob),跟现在完全一样。斑驳函数则会有FunctionBody_Await_Blob等。虽然仍然要在产生式中添加Blob下标,但已存在函数的FunctionBody的展开形式还跟以前一样。

不允许await用作标识符

接下来,我们要搞清楚在FunctionBody_Await中,是怎么不允许await用作标识符的。

仔细看一看产生式,可以发现从FunctionBody到之前的VariableStatement产生式都带_Await参数。因此,在异步函数中,就有VariableStatement_Await,而在非异步函数中,则有VariableStatement

再看仔细一点,注意一下参数。我们已经看到了这个VariableStatement[10]的产生式:

VariableStatement[Yield, Await] :
  var VariableDeclarationList[+In, ?Yield, ?Await] ;

所有VariableDeclarationList[11]的产生式也都照样带这些参数:

VariableDeclarationList[In, Yield, Await] :
  VariableDeclaration[?In, ?Yield, ?Await] 

(这里只展示与我们例子相关的产生式[12]。)

VariableDeclarationList[In, Yield, Await] :
  BindingIdentifier[?Yield, ?Await] Initializer [?In, ?Yield, ?Await]opt ;

这里的opt简写代表产生式右端符号是可选的,也就是实际上有两个产生式:一个带可选的符号(Initializer),另一个不带。

对我们简单的例子来说,VariableStatement包含关键字var,紧跟一个BindingIdentifier(没有初始化器Initializer),以分号结束。

为了允许或不允许await用作BindingIdentifier,我们希望最终看到这些:

BindingIdentifier_Await :
  Identifier 
  yield

BindingIdentifier :
  Identifier 
  yield
  await

这表示在异步函数中不允许await作为标识符,在非异步函数允许它作为标识符。

实际上规范中并没有给出这样的定义,而我们找到的是这个产生式:

BindingIdentifier[Yield, Await] :
  Identifier 
  yield
  await

展开后得到:

BindingIdentifier_Await :
  Identifier 
  yield
  await

BindingIdentifier :
  Identifier 
  yield
  await

(这里省略了BindingIdentifier_YieldBindingIdentifier_Yield_Await的产生式,因为我们的例子不需要。)

这么看awaityield任何时候都可以作为标识符。这是怎么回事啊?这篇文章难道白写了吗?

静态语义出马

原来为了在异步函数中禁止将await用作标识符,还需要用到静态语义

静态语义描述静态规则,也就是在程序运行前要校验的规则。

对我们的例子而言,BindingIdentifier的静态语义[13]定义了以下语法导向的规则:

BindingIdentifier[Yield, Await] : await
  • 如果这个产生式有[Await]参数就是一个语法错误(Syntax Error)。

实际上,这就是禁止BindingIdentifier_Await : await产生式。

规范解释说,之所以存在这个产生式但又通过静态语义将它定义为语法错误,是因为与 ASI(Automatic Semicolon Insertion,自动插入分号)冲突。

我们知道,在无法根据语法产生式解析一行代码时,ASI 就会介入。ASI 尝试添加分号以满足语句和声明必须以分号结束的要求。(关于 ASI 的详细介绍,可以看下一篇文章。)

来看下面的代码(规范中的例子):

async function too_few_semicolons() {
  let
  await 0;
}

如果文法不允许await作用标识符,ASI 就会介入并将上面的代码转换为下面这样文法正确的代码,这样let也会被当成标识符:

async function too_few_semicolons() {
  let;
  await 0;
}

与 ASI 的这种冲突被认为太令人困惑,因此才用静态语义禁止await作为标识符。

不允许标识符的StringValues

还有另一条相关规则:

BindingIdentifier : Identifier 
  • 如果这个产生式有[Await]参数,且IdentifierStringValue"await",就是一个语法错误(Syntax Error)。

乍一看不好理解。Identifier 的定义[14]如下:

Identifier : 
 IdentifierName  but not  ReservedWord 

await是个ReservedWord,因此Identifier怎么可能是await呢?

的确,Identifier不可能是await,但可以是StringValue"await"(字符序列await的一种不同的表示方式)的其他值。

标识符名的静态语义[15] 定义了如何计算标识符的StringValue。比如,a的 Unicode 转义序列是\u0061,因此\u0061waitStringValue"await"\u0061wait不会被词法语法识别为关键字,而会被识别为Identifier。静态语义禁止在异步函数中使用它作为变量名。

因此,这样可以:

function old() {
  var \u0061wait;
}

但这样不行:

async function modern() {
  var \u0061wait; // 语法错误
}

小结

通过学习这篇文章,我们了解了词法文法、语法文法,以及用于定义语法文法的简写形式。作为例子,我们研究了await在异步函数中被禁止用作标识符,但在非异步函数中则允许。

下一篇文章将介绍词法文法其他有意思的部分,比如自动插入分号(ASI)和包含文法(cover grammar)。

参考资料

[1] 文无关文法: https://en.wikipedia.org/wiki/Context-free_grammar

[2] 词法文法: https://tc39.es/ecma262/#sec-ecmascript-language-lexical-grammar

[3] Unicode码点: https://en.wikipedia.org/wiki/Unicode#Architecture_and_terminology

[4] 语法文法: https://tc39.es/ecma262/#sec-syntactic-grammar

[5] 正则文法: https://tc39.es/ecma262/#sec-patterns

[6] 数值字符串文法: https://tc39.es/ecma262/#sec-tonumber-applied-to-the-string-type

[7] 文法表示法: https://tc39.es/ecma262/#sec-grammar-notation

[8] 这个: https://tc39.es/ecma262/#prod-AsyncFunctionBody

[9] 相关产生式: https://tc39.es/ecma262/#prod-FunctionDeclaration

[10] VariableStatement: https://tc39.es/ecma262/#prod-VariableStatement

[11] VariableDeclarationList: https://tc39.es/ecma262/#prod-VariableDeclarationList

[12] 产生式: https://tc39.es/ecma262/#prod-VariableDeclaration

[13] BindingIdentifier的静态语义: https://tc39.es/ecma262/#sec-identifiers-static-semantics-early-errors

[14] Identifier的定义: https://tc39.es/ecma262/#prod-Identifier

[15] 标识符名的静态语义: https://tc39.es/ecma262/#sec-identifier-names-static-semantics-stringvalue

本文由哈喽比特于3年以前收录,如有侵权请联系我们。
文章来源:https://mp.weixin.qq.com/s/1bNRE2t7UBY1UNikC7MC6g

 相关推荐

刘强东夫妇:“移民美国”传言被驳斥

京东创始人刘强东和其妻子章泽天最近成为了互联网舆论关注的焦点。有关他们“移民美国”和在美国购买豪宅的传言在互联网上广泛传播。然而,京东官方通过微博发言人发布的消息澄清了这些传言,称这些言论纯属虚假信息和蓄意捏造。

发布于:1年以前  |  808次阅读  |  详细内容 »

博主曝三大运营商,将集体采购百万台华为Mate60系列

日前,据博主“@超能数码君老周”爆料,国内三大运营商中国移动、中国电信和中国联通预计将集体采购百万台规模的华为Mate60系列手机。

发布于:1年以前  |  770次阅读  |  详细内容 »

ASML CEO警告:出口管制不是可行做法,不要“逼迫中国大陆创新”

据报道,荷兰半导体设备公司ASML正看到美国对华遏制政策的负面影响。阿斯麦(ASML)CEO彼得·温宁克在一档电视节目中分享了他对中国大陆问题以及该公司面临的出口管制和保护主义的看法。彼得曾在多个场合表达了他对出口管制以及中荷经济关系的担忧。

发布于:1年以前  |  756次阅读  |  详细内容 »

抖音中长视频App青桃更名抖音精选,字节再发力对抗B站

今年早些时候,抖音悄然上线了一款名为“青桃”的 App,Slogan 为“看见你的热爱”,根据应用介绍可知,“青桃”是一个属于年轻人的兴趣知识视频平台,由抖音官方出品的中长视频关联版本,整体风格有些类似B站。

发布于:1年以前  |  648次阅读  |  详细内容 »

威马CDO:中国每百户家庭仅17户有车

日前,威马汽车首席数据官梅松林转发了一份“世界各国地区拥车率排行榜”,同时,他发文表示:中国汽车普及率低于非洲国家尼日利亚,每百户家庭仅17户有车。意大利世界排名第一,每十户中九户有车。

发布于:1年以前  |  589次阅读  |  详细内容 »

研究发现维生素 C 等抗氧化剂会刺激癌症生长和转移

近日,一项新的研究发现,维生素 C 和 E 等抗氧化剂会激活一种机制,刺激癌症肿瘤中新血管的生长,帮助它们生长和扩散。

发布于:1年以前  |  449次阅读  |  详细内容 »

苹果据称正引入3D打印技术,用以生产智能手表的钢质底盘

据媒体援引消息人士报道,苹果公司正在测试使用3D打印技术来生产其智能手表的钢质底盘。消息传出后,3D系统一度大涨超10%,不过截至周三收盘,该股涨幅回落至2%以内。

发布于:1年以前  |  446次阅读  |  详细内容 »

千万级抖音网红秀才账号被封禁

9月2日,坐拥千万粉丝的网红主播“秀才”账号被封禁,在社交媒体平台上引发热议。平台相关负责人表示,“秀才”账号违反平台相关规定,已封禁。据知情人士透露,秀才近期被举报存在违法行为,这可能是他被封禁的部分原因。据悉,“秀才”年龄39岁,是安徽省亳州市蒙城县人,抖音网红,粉丝数量超1200万。他曾被称为“中老年...

发布于:1年以前  |  445次阅读  |  详细内容 »

亚马逊股东起诉公司和贝索斯,称其在购买卫星发射服务时忽视了 SpaceX

9月3日消息,亚马逊的一些股东,包括持有该公司股票的一家养老基金,日前对亚马逊、其创始人贝索斯和其董事会提起诉讼,指控他们在为 Project Kuiper 卫星星座项目购买发射服务时“违反了信义义务”。

发布于:1年以前  |  444次阅读  |  详细内容 »

苹果上线AppsbyApple网站,以推广自家应用程序

据消息,为推广自家应用,苹果现推出了一个名为“Apps by Apple”的网站,展示了苹果为旗下产品(如 iPhone、iPad、Apple Watch、Mac 和 Apple TV)开发的各种应用程序。

发布于:1年以前  |  442次阅读  |  详细内容 »

特斯拉美国降价引发投资者不满:“这是短期麻醉剂”

特斯拉本周在美国大幅下调Model S和X售价,引发了该公司一些最坚定支持者的不满。知名特斯拉多头、未来基金(Future Fund)管理合伙人加里·布莱克发帖称,降价是一种“短期麻醉剂”,会让潜在客户等待进一步降价。

发布于:1年以前  |  441次阅读  |  详细内容 »

光刻机巨头阿斯麦:拿到许可,继续对华出口

据外媒9月2日报道,荷兰半导体设备制造商阿斯麦称,尽管荷兰政府颁布的半导体设备出口管制新规9月正式生效,但该公司已获得在2023年底以前向中国运送受限制芯片制造机器的许可。

发布于:1年以前  |  437次阅读  |  详细内容 »

马斯克与库克首次隔空合作:为苹果提供卫星服务

近日,根据美国证券交易委员会的文件显示,苹果卫星服务提供商 Globalstar 近期向马斯克旗下的 SpaceX 支付 6400 万美元(约 4.65 亿元人民币)。用于在 2023-2025 年期间,发射卫星,进一步扩展苹果 iPhone 系列的 SOS 卫星服务。

发布于:1年以前  |  430次阅读  |  详细内容 »

𝕏(推特)调整隐私政策,可拿用户发布的信息训练 AI 模型

据报道,马斯克旗下社交平台𝕏(推特)日前调整了隐私政策,允许 𝕏 使用用户发布的信息来训练其人工智能(AI)模型。新的隐私政策将于 9 月 29 日生效。新政策规定,𝕏可能会使用所收集到的平台信息和公开可用的信息,来帮助训练 𝕏 的机器学习或人工智能模型。

发布于:1年以前  |  428次阅读  |  详细内容 »

荣耀CEO谈华为手机回归:替老同事们高兴,对行业也是好事

9月2日,荣耀CEO赵明在采访中谈及华为手机回归时表示,替老同事们高兴,觉得手机行业,由于华为的回归,让竞争充满了更多的可能性和更多的魅力,对行业来说也是件好事。

发布于:1年以前  |  423次阅读  |  详细内容 »

AI操控无人机能力超越人类冠军

《自然》30日发表的一篇论文报道了一个名为Swift的人工智能(AI)系统,该系统驾驶无人机的能力可在真实世界中一对一冠军赛里战胜人类对手。

发布于:1年以前  |  423次阅读  |  详细内容 »

AI生成的蘑菇科普书存在可致命错误

近日,非营利组织纽约真菌学会(NYMS)发出警告,表示亚马逊为代表的电商平台上,充斥着各种AI生成的蘑菇觅食科普书籍,其中存在诸多错误。

发布于:1年以前  |  420次阅读  |  详细内容 »

社交媒体平台𝕏计划收集用户生物识别数据与工作教育经历

社交媒体平台𝕏(原推特)新隐私政策提到:“在您同意的情况下,我们可能出于安全、安保和身份识别目的收集和使用您的生物识别信息。”

发布于:1年以前  |  411次阅读  |  详细内容 »

国产扫地机器人热销欧洲,国产割草机器人抢占欧洲草坪

2023年德国柏林消费电子展上,各大企业都带来了最新的理念和产品,而高端化、本土化的中国产品正在不断吸引欧洲等国际市场的目光。

发布于:1年以前  |  406次阅读  |  详细内容 »

罗永浩吐槽iPhone15和14不会有区别,除了序列号变了

罗永浩日前在直播中吐槽苹果即将推出的 iPhone 新品,具体内容为:“以我对我‘子公司’的了解,我认为 iPhone 15 跟 iPhone 14 不会有什么区别的,除了序(列)号变了,这个‘不要脸’的东西,这个‘臭厨子’。

发布于:1年以前  |  398次阅读  |  详细内容 »
 相关文章
Android插件化方案 5年以前  |  237231次阅读
vscode超好用的代码书签插件Bookmarks 2年以前  |  8065次阅读
 目录