|
JavaScript 2.0
根拠
構文
|
11/19/2002 (Tue)
この節では草案の開発中に考えられていた他の構文を幾つか示す。
文間のセミコロンを省略してもプログラムが記述できるのは、暗黙のセミコロンの挿入があるためである。JavaScript 1.5 、JavaScript 2.0 はいずれも2種類のセミコロン挿入を持つ:
} やプログラムの終端の前のセミコロンは JavaScript 1.5 、JavaScript 2.0
のいずれでも省略可能である。これに加えて JavaScript 2.0 パーサは if-else 文の
else の前と do-while 文の while
の前のセミコロンの省略も認めている。文法的セミコロンの挿入は構文文法の生成規則により直接実装 (実現) され、上記の場合は単純にセミコロンを必要としない。ソースコード中の改行は文法的セミコロンの挿入には無関係である。
改行におけるセミコロンの挿入を構文文法で実装 (実現) するのは容易ではない。この種のセミコロンの挿入は構文的に誤ったプログラムを正しいプログラムに変えてしまい、ソースコードの改行にも依存する。
文法的セミコロンの挿入は無害なものである。一方、改行におけるセミコロンの挿入には以下に示す問題がある:
最初の問題は、XML 属性を扱い、改行を空白に変換するプリプロセッサの構築を困難にする。2番目と3番目の問題はより深刻である。プログラマは
a = b + c (d + e).print()
というプログラムが以下のようになると考えるが
a = b + c; (d + e).print();
実際にはこのプログラムは以下のようにパースされる:
a = b + c(d + e).print();
このことが分かるとプログラマは混乱する。
3番目の問題は最も深刻なものである。新しい機能が言語に追加されると誤った構文は正しいものになる。仮に既存のプログラムが改行におけるセミコロンの挿入を発生させるために誤った構文を使っているとしたら、機能が追加された途端、暗黙の内にそのプログラムの挙動は変更されてしまう。例えば
(4 "in" のように) 数値リテラルの後ろに文字列リテラルを並べて書くと JavaScript 1.5 では不正だが、JavaScript
2.0 においては単位式として構文的に正しいものである。残念ながらこの構文の拡張は次のような JavaScript 1.5 プログラムの意味を変更してしまう:
a = b + 4 "in".print()
JavaScript 1.5 で以下のようになっていたものが
a = b + 4; "in".print();
JavaScript 2.0 では次のようになる
a = b + 4"in".print();
JavaScript 2.0 では数値リテラルと文字列リテラルを同じ行に置くことを要求する [no line break] 制限を文法に追加することにより、この不一致を克服している。残念ながらこの互換性は諸刃の剣であり、JavaScript 1.5 との互換性のために JavaScript 2.0 は非常に多くの [no line break] 制限を持たなければならなくなる。その全てを漏らさず設定することは困難であり、1つでも忘れると JavaScript 2.0 プログラムで (先に述べた) 再解釈が起こることになる。また、
internal
function f(x) {return x*x}
というコードが
internal function f(x) {return x*x}
のようにならず、
internal;
function f(x) {return x*x}
となることでプログラマは驚くだろう (internal; は式文である) 。
JavaScript 2.0 の初期のバージョンでは改行におけるセミコロンの挿入を認めていなかった。現在は非 strict モードにおいてのみ認められており、strict モードでは言語の単純化のために [no line break] 制限は全て取り除かれる。これらの副作用として strict モードと非 strict モードで異なる振る舞いをするプログラム (上に挙げた例のような) を書くことができるが、これは単純さのための代価に過ぎない。
JavaScript 2.0 は正規表現リテラルを検出する規則に JavaScript 1.5 と同じものを採用することで互換性を保持している。この方法ではパースせずに JavaScript プログラムから全てのトークンを見つけることが不可能になるため、構文指向テキストエディタやマシンスキャナといったプログラムの設計が困難になる。
JavaScript 2.0 の字句文法を構文文法から独立させた場合、ツールは JavaScript プログラムを簡単に扱えるようになり、例えば
JavaScript 2.0 或いはそれ以降のプログラムを正確に HTML ページに埋め込むために、例えば </
といったものを全て避けることができる。完全なパーサは JavaScript のバージョン毎に変わる。このような困難を理解するには以下の JavaScript 1.5
コード片を比較するとよい:
for (var x = a in foo && "</x>" || mot ? z:/x:3;x<5;y</g/i) {xyz(x++);}
for (var x = a in foo && "</x>" || mot ? z/x:3;x<5;y</g/i) {xyz(x++);}
JavaScript 2.0 の設計で早期に検討されていた代替案に、正規表現の明確な構文と新しい構文の使用があった。RegularExpression
はその開始、終了デリミタに / と / の代わりに « と »
を使用することが明確に示されてた。例えば «3*» は0個以上の 3 にマッチする正規表現である。«»
とすると空の正規表現となり、空文字列だけにマッチする。一方 // はコメントの開始になる。JavaScript 1.5 で空の正規表現を記述する場合は
/(?:)/ としなければならない。
JavaScript の将来のバージョンの機能で記述されたプログラムの部分をレキシカルアナライザが読み飛ばすためにブロックの終点
(} が相当する) を探さなければならないとき、構文の再同期が起こる。通常これは問題では無いが、正規表現により字句解析が構文解析に依存するようになるので事は複雑になる。正規表現リテラルの解釈に使う規則はプログラムの各部分において変えなければならない。以下の規則は正しく機能するか、或いはその簡易解析は正規表現の位置を決定するための入力に基づいて処理される。これについては更なる作業が必要である。
構文の再同期において JavaScript 2.0 は / が正規表現の開始なのか、除算 (或いは /=) 演算子なのかを前のトークンだけに基づき決定する。
/ の解釈 |
前のトークン |
|---|---|
/ か /= |
Identifier Number RegularExpression
String) ++ --
] }class false null
private protected public
super this trueget include setその他の区切り子 |
| RegularExpression | ! != !==
# % %=
& && &&=
&= ( *= + +=
, - -=
-> . ..
... / /=
: :: ;
< << <<=
<= = ==
=== > >=
>> >>= >>>
>>>= ? @
[ ^ ^=
^^ ^^= {
| |= ||
||= ~abstract break case
catch const continue
debugger default delete
do else enum
export extends final
finally for function
goto if implements
import in instanceof
interface is namespace
native new package
return static switch
synchronized throw throws
transient try typeof
use var volatile
while with |
// は前のトークンに関係無く、コメントの開始と解釈される。
ただ1つ議論の余地のある選択は ) と } である。) か } の後ろの /
は除算のシンボル () か } が部分式かオブジェクトリテラルを閉じる場合) と正規表現トークン
() か } が前の文、if 、while 、或いは for 式を閉じる場合)
のいずれにもなり得る。(x+y)/2 のような式で / を RegularExpression
として解釈すると問題があるので、) か } の後ろでは除算演算子として解釈する。正規表現リテラルを式文の先頭に置きたい場合は、正規表現を括弧内に記述するのが最善である。幸い、正規表現操作の結果を変数に代入するのが普通なのでこのようなことは一般的ではない。
現在の草案では JavaScript 2.0 は宣言中に型を含める場合にパスカルスタイルのコロンを使うようになっている。例えば:
var x:Integer = 7;
function square(a:Number):Number {return a*a}
これは ECMA ワーキンググループの決定によるが、Waldemar だけは賛成していない。これには他に2、3の構文が考えられる:
関数の戻り値の型を引数リストの後ろに書く修正 C スタイルが考えられる:
var Integer x = 7;
var Integer y = 8, Integer z = 9; // Integer 型変数を2つ宣言する
function square(Number a) Number {return a*a}
文法が曖昧になるため、関数の戻り値の型を引数リストの前に置くことはできない。
実際には実装は TypedIdentifier と Result 文法規則を以下で置換することにより、パスカルスタイルと修正 C スタイルの両方の宣言を許容できる。最終的な文法は LALR(1) のままである。
修正 C スタイル構文を使用することの利点は以下の通りである:
{a:17, b:33})
の構文に使用されている。オブジェクトリテラル中でフィールドの型を宣言する場合に後者は特に厄介である。プログラマの中には関数に名前付き引数を渡すのに便利な手段としてこれらを使用している者もいる。[訳注:
例えば以下のようなものである:]
// JavaScript 1.5 で擬似名前付き引数
function divide(arg) {
assert("numerator" in arg && "denominator" in arg);
return arg["numerator"] / arg["denominator"];
}
// 或いは
// function divide(/* ... */) {
// assert(arguments.length > 0);
// assert("numerator" in arguments[0] && "denominator" in arguments[0]);
// return arguments[0]["numerator"] / arguments[0]["denominator"];
// }
divide({numerator : 6, denominator : 2}); // 3属性は簡単な式であるので型として評価することもできるだろう。var 宣言と const
宣言についてはこれらの属性は宣言された変数の型となる。function 宣言についてはそれらの属性は関数の戻り値の型となる。一貫した文体のために引数の型は識別子の前に書く。
Integer var x = 7;
Integer var y = 8, z = 9; // Integer 型変数を2つ宣言する
Number function square(Number a) {return a*a}
このスタイルは簡潔であり、見た目も幾分自然である。
繰り返しになるが実装はパスカルスタイルと属性スタイルの両方の宣言を許容でき、その最終的な文法は LALR(1) のままである。しかしながら2つ或いは3つのスタイルを使うことによる混乱を起こすよりも、どれか1つに決めてしまった方が良い (この柔軟性は既存のプログラムとの互換性のために使用できるだろうが)。
以下の理由により、JavaScript 2.0 では型式に値式と同じ構文を使用する:
(expr1)(expr2) という式で expr1
は型式、値式のどちらだろうか? 同じ構文を使っていれば問題無い。ECMA TC39 モジュラリティ サブコミッティの統一見解により、ゲッタの定義に getter function id (...)
ではなく function get id (...) を、セッタの定義に
setter function id (...) ではなく
function set id (...) を使用することになった。前者の構文を使用するためには
FunctionName は以下のように簡単化される:
更に2つの属性 getter 、setter を追加する必要がある。この決定はいずれの構文も他方より実装が困難になることはないという美学に基づいた。
初期の草案でプラグマの代替案となっていたものは、構文エラーを、問題となっている文が解析されたときではなく実行されたときに出していた。この方法では単一のプログラムに将来のバージョンの
JavaScript で書かれた部分を含むことができた (そのバージョンの JavaScript
を解釈できないシステムがその部分を実行しない限りはエラーにならないというわけである)。エラーを含む箇所が実行されない場合、そのエラーがスクリプトを破壊することもない。例えば以下の関数は
whizBangFeature が false であれば正しく終了する:
function move(x:Integer, y:Integer, d:Integer) {
x += 10;
y += 3;
if (whizBangFeature) { // [訳注: 勢いよく飛んで行ってどこかにぶつかるという意味?]
simulate{@x and #y} along path
} else {
x += d; y += d;
}
return [x,y];
}
simulate{@x and #y} along path というコードは構文的には誤りであるが、スクリプトがこのコード片を実行しない限りはエラーによりスクリプトが破壊されることはない。
このアプローチの問題はデバッグが巧くいかないことである。構文エラーは実行時ではなくコンパイル時に発見できなければどうしようもない。
|
Waldemar Horwat 最終更新: 2002年11月19日 (火) |