風の吹くまま気の向くまま、誰が言ったか知らないが言われてみれば確かに REPL (node.js)

Pygments で lexer 書こうなんてぇと、放っといても色んなもんに目移りする。

「lexerを自分で書く」という行為そのものも「気が散るに足るおもろいオモチャ」に囲まれる環境に身を置くことにもなるわけなんだけれども、書こうとしてるのが「lex/yacc」だ、ってのもこれをさらに増強するんであってな。

そもそも Pygments lexer を書くからには、対象となる言語のサンプルがたくさん欲しいわけなんだけれどもね、lex(flex)/yacc(bison) となってくると、「サンプル」として探そうとすると今はほとんど見つからない。どんだけ需要ないんだ、てことな。だからといって OSS から探してくるのもかなり大変だ。ワタシに心当たりがあるものはいくつかあるけれど、知らないものまで探しにいくのはさすがに大変だ。

なわけで、オライリー本を何年かぶりに読み直したり、そもそも書きかけ lexer で何度も同じもんみてるうちに、かなり記憶が蘇ってきたので、だったら自分でサンプル書いちゃおうかな、と。

で、lex 適用のサンプルとして手軽で良さそうなのが「javascript/css の minify」かな、と思った、というのが、連想ゲームその壱。

でな。「minify」に付きまとうのはさ、「変形によって意味が変わってはならない」ことでしょ? もっと言えば、「空白文字の数の増減によって意味が変わりうるような書き方はあるや、否や」ということも問題になる。これは言語の設計にも関係する。

空白問題で有名なのは、大昔の FORTRAN だ。マリナー1号計画の失敗で発覚したバグは「ピリオドとカンマを打ち間違える」ことによって起こったが、これは「打ち間違えた」というコーディングミスだけでなく、「空白はあってもなくても良い」という FORTRAN の言語仕様によるものだった。「DO 5 K=1,3」は、行番号5までのブロックを、変数Kを1から3まで動かしながらループ」を意味する。「DO 5 K=1.3」は、空白に意味があったならば文法エラーだったところだが、当時の FORTRAN は DO5K=1.3 という代入だと翻訳した。(エラそーに解説してるが、手許にそんな古い FORTRAN 動かせる環境なんかもちろん持ってない(し資料だって無論あるわけない)ので、確認しながら書いてるわけじゃない。)

つまりは javascript にその手の悩ましい仕様はあったかいな、と気になりだしたのさ、と、連想ゲームその弐の開始。空白文字の増減の話とは違うけれど、当然

1 // comment
2 var a = 1;

これをこのまま改行を空白に置換したらダメ:

1 // comment var a = 1;

そもそも minify なのでコメントは取り去るわけだから関係ないけどね。

でさ。前から気に喰わない javascript の仕様。「セミコロンはあってもなくてもいい」というヤツね:

1 // comment
2 var a = 1 // valid
3 var b = 2;

bourne-shell なんかのシェルスクリプトと一緒だから気にしなくていいじゃないか、と思う人は、単純に間違い。bourne-shell などのシェルスクリプトは「原則として行指向」であって、要するに「改行に意味がある」。具体的には、「改行は、ステートメント区切りとして機能する」。こういうこと:

 1 #! /bin/sh
 2 echo "aaa"
 3 echo "bbb"
 4 # ↑この2行は、2つの echo を呼び出す
 5 
 6 echo "aaa" echo "bbb"
 7 # ↑この「2行のつもり?」は「1行のステートメント」としてしか解釈されない。当たり前だろ!
 8 #  当然 /bin/sh は忠実に「aaa echo bbb という3つの引数で echo を実行する。
 9 
10 echo "aaa" ; echo "bbb"
11 # ↑セミコロンはまさにこのために使う

よーするに javascript って行指向ではないわけじゃん。いくらパーサが空気読んで解釈出来るとしたって、本当は「人間のため」には厳格に「セミコロンを付けねばならぬ」としといた方がいいんよ。minify でこうなって、あなた、すぐさま読める?

1 s="a b c" a=1 b=2 c=a*3+2

この方が普通に読みやすいと思うし:

1 s="a b c";a=1;b=2;c=a*3+2

まぁ minify 後のコードに読みやすさ求めてどーすんだ、てのはあるけどさ。

と、ぼやーっと考えてて、そういや javascript/ecmascript の lint なんてあるだろうなぁ、あればセミコロン付けろやオラァなんて叱ってくれるだろうなぁ、という連想ゲームその参開始。

「ecmascript lint」と検索かけるとほぼトップで ESLint (GitHub) がヒットする。おやおや、GitHub 側が先にヒットしちまうのね。いきなりソースツリーを読むハメになったが、さらっとみる限り、「実行すべきもの」としては js しか見当たらない。うーん、どうやって動かすんだろう。ブラウザから動かすとか? これをホスティングしてる WEB サービスがあったりすんの? それとも node.js 前提?

Installationで「npm でインストールする」と書いてある。サイトのどこにも npm の説明は書いてない。うーん、linux 前提とか? nからはじまるところをみるに、node.js 関係かなぁ? とちょちょっと検索したら、正解。node.js に一緒に付いてくる(パッケージマネージャ)。

そもそも「いれりゃぁいいのに」node.js、随分前に「ぶっ壊れる前の旧PC」で入れてちょっと遊んだっきり、「いれようともしてなかった」のは、そりゃ「マイブームの向くまま気の向くまま」な生来の怠け癖によるもんです。今にして思えば こんなネタなんかも、node.js 環境を活用してりゃ、も少しすむーじーだったかもしれんわな。どりゃ、久々に入れっちまおうか。

ESLint (GitHub)のサイトを読んでると、これの元となった JSHint についても触れている。また、ESLint は AST ベースだと書かれてる。JSHint もそうなのかな。「解析済み構文木に基く」ってことは、「セミコロン付けろやヲラァ」はチェック出来ないだろうなぁ、と、入れる前から思う。

さらにちょちょっと検索していると、JSLint てのもある、てこともわかった。node.js インストールが済んで。うーん、全部入れちゃえ:

プロンプトに注目
1 me@host: system32$ npm install jshint jslint eslint

あら、管理者権限が必要だ、と言われた。ワタシはバカ素直なので、「管理者権限として実行」してインストール、出来た…、てない

「管理者権限が必要だ」で気付けよ、つぅ話。ダメなパッケージマネージャだ、なのか、単にアタシがアホなだけなのか。デフォルトでは「カレントディレクトリに」インストールしちゃうらしい。なんで c:/Windows/SYSTEM32 なんかにいたかというと。ワタシ、MSYS の bash を「直接」起動するヒトなのね。起動用のバッチを介さずに。面倒だから。ただそうすると起動時のカレントディレクトリがここになるんです。普段はそこからすぐに目的の場所に cd するんだけど、この手の「グローバル系(とワタシが勝手に思った)」の作業は、カレントあんまし気にしないもんで。

ちょっと時間損した:

プロンプトに注目
1 me@host: system32$ npm install --global jshint jslint eslint

ちゃんと ESLint (GitHub) に書かれてんのに、バカねぇ。

ではでは…

1 me@host: ~$ echo -e '"use strict";\nvar a = 1\nvar b = 1\n' | eslint -
2 me@host: ~$ echo -e '"use strict";\nvar a = 1\nvar b = 1\n' | jslint -
3 me@host: ~$ echo -e '"use strict";\nvar a = 1\nvar b = 1\n' | jshint -
4 stdin: line 1, col 1, Use the function form of "use strict".
5 stdin: line 2, col 10, Missing semicolon.
6 stdin: line 3, col 10, Missing semicolon.
7 
8 3 errors
9 me@host: ~$ 

まずどれも標準入力から受け取れるのがステキだ。皆 Unix 育ちなのかな。少なくともデフォルトでセミコロンの抜けを指摘してくれたのは jshint だけみたいだ。

うーん? AST ベースでもチェック出来るもんなのかな? それとも jshint は自前パーサを持ってる? 前者ならほかのもチェックオプションでチェックしてくれたりもすんのかしら?

ご察しの通りワタシは一度も「生粋の WEB 系エンジニア」だったことはないわけで、「”use strict”」なんて、ワタシは今の今まで知らなかったんだけど、JSHint のドキュメントをつらつら眺めてたら目に留まったので、へぇ、ecmascript もやはりここへ進化したんだなぁ、と思ってやってみた。でもサンタさんが街にやってきた記録をみるに、あらま、登場して結構経ってんのね。

でな。「Use the function form of “use strict”.」なる次の連想ゲームが始まるわけだ。なんだろこれ、と。

無論こんな情報は一発で見つかる。あぁ、あれか。「ブラウザ内臓言語」として誕生した javascript の宿命、だよね。「名前空間汚しまくり問題」。要は「グローバルに “use strict” すると貴方のじゃないスクリプトをぶっ壊してしまうので「包め」」ということをおっさってんのね:

こうじゃなくて
1 'use strict';
曰く、こうせい
1 (function () {
2    'use strict';
3    // this function is strict...
4 }());

こうしときゃ、「貴方の作ったもんだけが安心」に出来ろ、とな。なる。

なんとなく lint 系の情報収集は個人的にちょっと満足してしまったので、今度は node.js 本体で遊んでみたくなった。

まず「起動」して驚く。あー。インタラクティブに動いてら。前に入れたときは試さなかったのかなぁ? 全く記憶にない。それとも最近出来たのかな? しかもこの readline、独特で面白い。画面クリアの動きが気持ちいい。

さて、発端となった「空白文字の数の増減によって意味が変わりうるような書き方はあるや、否や」をやってみようかしらね、と、この対話モードで試そうとして、困り果ててしまった。なんだこれ? .break だって、「ドキュメントを読んで」どうにか辿り着いたが、二次プロンプトから抜ける術がなくて泣きそうになった。

「対話実行モード」の実現は難しい、という話は前にも書いた。登場初期は不安定な場合はやっぱ多いよ。bash にその記憶はないけれど。けどな、このワタシが見つけたバグは、それとは違うしょーもないもんだと思う。要するに「入力が不完全」であることを検知して入力を促す、というのが、対話的シェルの(明示的な継続行指示を必要としない場合の)基本的なマルチライン編集なんだけど、「エラーの種類の区別がおかしい」がゆえにこれは起こっている。入力を続けても正しい完全な入力に出来る術はないので、「SyntaxError⇒揉み消して入力を促す⇒SyntaxError⇒揉み消して入力を促す⇒以下同文」という無間地獄に陥っておるわけな。

なんかね、この対話実行モード、REPL てなもので作ってるらしいんだけど、issues を repl でフィルターしてみると、まぁ出てるわ出てるわ。皆今は苦労して使ってる感じなのかいね?