Posted at

ECMAScript(JavaScript)の仕様をちょっと読んでみよう


イントロ

MDN はもはや業界標準の JavaScript リファレンスサイトであるが、JavaScript の syntax (構文) や低レベルな動作については情報が載っていないことがある。このような場合は仕様に立ち返ってみる必要があるが、ECMAScript の仕様は非常に分かりにくい。この記事では、ECMAScript の仕様がどういうルールで書かれているか、どう読めばいいかを軽く説明する。


if 文の syntax の仕様ってどうなってるの?

if 文は statement1 であるから、"13 ECMAScript Language: Statements and Declarations" の中で説明されている。

https://tc39.es/ecma262/#sec-if-statement

早速最初の syntax1 のところを見てみると、下のように分かりにくい式のようなものが書かれている。



https://tc39.es/ecma262/#prod-IfStatement

実はこれは文脈自由文法を表している。


文脈自由文法(Context-Free Grammar)

多くの言語の syntax1 は文脈自由文法で表現でき、この仕様でもECMAScriptの syntax を規定するのに文脈自由文法を用いている。

文脈自由文法は、いくつかの記号生成規則によって「文字列の集合」を表現するものである。簡単に言えば、一つの記号からはじめて、生成規則に基づいて繰り返し置換を繰り返すことによって最終的な文字列を得るというものである。当然どの規則をどの順番で使うかによって色々な文字列を生成することができるわけだが、その「生成できる文字列すべて」が「その文法が表す文字列集合」となる。ソースコードも所詮文字列であるから、ある1つの文脈自由文法によって「プログラムとして有効な文字列とは何か」というのを表現できるというわけである。簡単な例は以下のようなものである。


  • 非終端記号: S, T

  • 終端記号: a, b

  • 開始記号: S

  • 生成規則:



    • S -> S T


    • S -> T


    • T -> a


    • T -> b



非終端記号は文字列を生成する過程で使われる中間的な記号であり、開始記号からスタートして、非終端記号がすべてなくなるまで置換を繰り返す。それぞれの生成規則は、左辺の非終端記号は右辺で置換できるということを表す。同じ左辺をもつ生成規則は複数あってもよく、その場合どの生成規則を適用してもよい。終端記号は、最終的な文字列に出現しうる記号であり、置換することはできない。

上の文法から生成される文字列は何だろうか?例えば以下のように abb を生成することができる。

同様に abaaaabbab なども生成でき、正規表現で表すと /(a|b)+/ となる。この文法はたまたま正規表現で表現できるが、正規表現で表現できない文脈自由文法もあり、文脈自由文法の方が表現能力が高いことが知られている2


もう一度 if statement の syntax を見てみよう

仕様にある syntax では、イタリック体が非終端記号太字が終端記号を表している。先程見たこの syntax は

以下の生成規則を表している(簡潔のため Yield などは無視する)。



  • IfStatement -> if ( Expression ) Statement else Statement


  • IfStatement -> if ( Expression ) Statement

IfStatement が if 文を表す非終端記号であり、それは上のような記号列で置換できる、ということである。この規則から if の条件式には 「Expression から生成される文字列」を書くことができ、それ以外はダメだということが分かる。

ExpressionStatement はそれぞれプログラミング用語の expression1、statement1 を表しており、「確かに if 文の syntax ってこうだよな」とおそらく読者の半分くらいは納得できることだろう。


if 文といえば { }がつくはずだけど、無いのはなぜ?

と思うかもしれないが、そもそも { } は必須ではない。私達がよく書く{ } は実は Statement の一部である。では、 Statement から生成される記号を確認してみよう。

NOTE: 仕様のページ上で非終端記号をクリックすればその生成規則のところにジャンプできるので活用しよう。

下のように Statement が左辺にある生成規則はたくさんあるが、天下り的に言うと{ }BlockStatement から生成される。



https://tc39.es/ecma262/#prod-Statement

BlockStatement の生成規則を見ると下のように右辺は Block だけであり、さらに Block の生成規則を見ると確かに { } が両端にあることが確認できる。



https://tc39.es/ecma262/#prod-BlockStatement

以上を踏まえると、例えば下の if 文はこのような構造になっていることが分かる。


Static Semantics って何?

if 文の syntax のを見ると、Static Semantics がいくつか並んでいることがわかる。これは、コードの静的解析(文脈自由文法だけでは表現できない、言語上の制約をチェックする)を行うために実行環境が実行する処理である。例えば、同じ名前の変数を letconst で複数宣言することができないが、このチェックは Static Semantics で行われている。


Runtime Semantics って何?

「実行すると内部的に何が起きるのか」という、その syntax の「意味」を表す。私達が普段使用するWebブラウザはこのルールに従ってJavaScriptを実行する(と期待されている)わけである。

では if 文の Runtime Semantics を見てみよう。



https://tc39.es/ecma262/#sec-if-statement-runtime-semantics-evaluation

「Evaluation」という名前がついているが、evaluate とは「値を計算する」「実行する」という意味である。JavaScript においては if 文などは戻り値が存在しないが、仕様上ではどの statement も「正常に評価し終わった」「エラーが発生した」「breakで抜けた」などの情報を含む Completion Record というものを返すことになっている。

仕様内で「evaluate 〇〇」とでてきたら、それは〇〇の Evaluation を実行する、という意味である。同様に、「the result of evaluating 〇〇」とあったら、それは〇〇の Evaluation を実行して Return されたものを指す。上の if 文の Evaluation を見ると、さらにその Expression や Statement に対して再帰的に Evaluation を実行していることが分かる。

else ありの方の手続きを簡単に説明すると以下のようになる。我々の直感と一致していることが確認できるだろう。

「では、ToBoolean はどういう処理をしているんだろう?」と気になったなら、ぜひ定義の部分を読んでみてほしい。


そういえば開始記号は何?


The syntactic grammar for ECMAScript is given in clauses 11, 12, 13, 14, and 15. This grammar has ECMAScript tokens defined by the lexical grammar as its terminal symbols (5.1.2). It defines a set of productions, starting from two alternative goal symbols Script and Module, that describe how sequences of tokens form syntactically correct independent components of ECMAScript programs.

(from 5.1.4The Syntactic Grammar)


Script もしくは Module が開始記号であると説明されている。ブラウザでいうとこれらは以下のものに該当する。



  • Script: 通常の <script>


  • Module: <script type="module">

後者については近年ブラウザに実装された機能であり、JSファイルから別のJSファイルを import することができる。詳しくは MDN を参照されたい。

それぞれの生成規則を見ると、たしかに Module はトップレベルでの import と export が許されていることが分かる。


まとめ

もちろんここまでで述べたことだけで仕様のすべてを理解できるようになる訳ではない(筆者も理解できてない)。特に、Execution Context、Job(非同期処理) あたりはかなり複雑である。しかし、この記事で「なんとなくどういう感じで書かれているか」を感じ取って貰えれば幸いである。





  1. 筆者の別記事を参照されたい。 



  2. ただし、実際のプログラミング言語で実装されている正規表現は理論的な正規表現を少し拡張したものであり、表現能力が少し高い。