SHJSのブラウザでの実行時間を削るには sh_main.js(SHJSのメインスクリプト)を速くするか、正規表現を効率的なものにする方法がある。(>遅い正規表現(20080116p01))。
正規表現に関してできることは限られるうえ、知識も少ない(『詳説 正規表現 第三版』待ち)ので、可能な限り文字クラスや文字集合といわれるものを使うように気を付けただけにとどまる。(sh_ruby.js, sh_javascript.js)
メインスクリプトの sh_main.jsに対してできることは多い。この日記の現在?の最新ページ(2008年1月12日から7日間)を表示して、sh_highlightDocument()前後での経過時間を表示したところこのようになった。
Firefox2 | IE7(64-bit) | IE7(32-bit) | Opera9.25 | |
---|---|---|---|---|
sh_main.js (0.4.2) | 935ms | 1050ms | 1270ms | 1260±150ms |
改変版 | 600ms | 680ms | 865ms | 1200±150ms |
削減率 | 36% | 35% | 32% | 5% |
ハイライト対象が少なくて数ミリ秒で処理が終わるような場合はオーバーヘッドのために改変版の方が 1-2ミリ秒遅くなるが、それよりもスクリプトがブラウザをロックする時間が長くなるような場合にこそ速度改善が必要なので OK。
代償としてファイルサイズが sh_main.jsで 10.5KiBから 12.7KiBへ +2.2KiB。jsmin圧縮後の sh_main.min.jsで 6.22KiBから 7.82KiBへ +1.60KiB。Apacheによる gzip圧縮やブラウザのキャッシュに期待します。
普段は全く Operaを使わないし、詳しくもない。むしろ Operaではキーボードを使ったブラウジングもままならない。そんな人間が Firefox+Firebugを頼りに sh_main.jsの修正を行ったので Operaの速度が改善しないのは仕方のない部分がある。(IEは改善したが)。(あんだけいじってトータルで変わらない方がすごい。どこが足を引っぱっているのだろう)。リテラル文字列と Stringオブジェクトの差が他のブラウザより大きいらしいが、それが原因?
EfficientJavaScript - Dev.Opera - 効率的な JavaScript (www.hyuki.com)
Operaでの JavaScriptの実行時間が他のブラウザに比べて長いのははっきりした理由があって、Operaはスクリプトが全力疾走中であってもユーザーの操作に対する反応を後回しにしたりしない。これは偉い。ユーザーを待たせない代わりにスクリプトが遅れるのは当然の代償で仕方がない。
あ、スクリプトでなく再描画が律速してるから改善しないということ?
(常に最新版だが一時的にバグが混入していることがあるかも)
すぐ上のリンク先はすでに変更が反映されている。
これら二つの記事を参考に escapeHTML()を変更した。測定に使ったページでは 9000回ちかく呼び出されるメソッドなので影響はバカにならない。といっても 600msだったのが 590msを切るようになった、というレベル。むしろ下請けfunctionを隠蔽できたことの方が嬉しい。
escapeHTML()自体、sh_builderのインターフェイスではないので、外部から呼び出せないようにすべきかもしれないが、functionをかぶせるたびに呼び出しのオーバーヘッドが増える気がしてそうはしていない。
SHJSの patternStackオブジェクトは外部と完全に独立して動作するのに、sh_highlightString()が呼ばれるたびに無名クラスとそのインスタンスを作成するような方法がとられている。コンストラクタと prototypeを書こう。(sh_highlightString()は HTML文書内の <pre class="sh_XXX">の数だけしか呼ばれないから影響は小さいが。件のページでは 58回)。
sh_highlightString()からしか使われないのにスタックの可視範囲が広がるのが気になるなら、さっき覚えた無名functionで二つをくるんでしまえば良い。
var sh_highlightString = (function(){ var Stack = function(){ this.stack_ = []; }; Stack.prototype.getLength = function(){/* ... */}; // …… return function(){ var patternStack = new Stack(); /* sh_highlightStringの中身がつづく…… */ }; })();
まあ、速度が改善するわけではないので、書き直さないんだけど。
innerHTMLや textContent、innerTextの使用は堕落だという気もするが、冗長な上に呼び出しを重ねることで遅くなる DOMメソッドがいけない。
口をあまり開けないで「ア」と発音したらワーニン(グ)もウォーニン(グ)に聞こえる。どっちでもいい。どっちゃでもいい。これこそどちらでもよい。オーストラリアの人の発音が知りたいこの頃。
ジーユーアイと読んで悪いことはないと思うが、Wikipedia(ja)にも載っているように、グイと読むと知ったときはひっくり返った(比喩)。
イーシーエムエースクリプトと読んでいた。YouTubeで Googleの人がエクマスクリプトと喋っていて、なにそれ??? もうエクマにも慣れたが。
けっこう前からアイトリプルイーだと知っている。最初はアイイーイーイーと読んでいた。トリプルって言いにくいよね。どうしても四音節になる。
今も頭の中でダブルさんスィーと読んでいる。ス・リーより さんの方が早い。w3mはもちろんダブルさんエムと読んでいる。
ウッシュと読むような気がする。こう読むんだよって(おそらく) Microsoftのコラムで読んだのだけど、英語だったので、その英語はなんと読むんですか? PostgresQLのように mp3ファイルを置いておいてくれると助かります。
読めないから WSHは(頭の中でも)読んでいない。Wと Sと Hが並んだもの、から、JScript、VBScript、ActivePerl、ActiveScriptRubyといった各種スクリプトエンジンをホストして Windowsや IEで実行可能にするアレ、という認識に直結している。
ロカールと読む必要を感じない。カタカナだとだいたいロケールと書いてある。あれっ、ロケールが正解?
6問目がわからない。UTF-7とか出てくる前にもう。
今年も期待してました。(去年の結果、一昨年の結果)。口説き力はおいといて今年の日本語テストの結果は
間違えたのは
第8問 部長(上司)に対する敬語で不適切なものは、どれ?
- 京都のおみやげです。部長、どうぞ頂いてください。
- 京都のおみやげです。部長、どうぞ召し上がってください。
- 京都のおみやげです。部長、どうぞお食べください。
第9問 優位な立場を利用して高圧的な態度をとる意の「かさに着る」の「かさ」。正しい書き方は、どれ?
- 暈(かさ)に着る
- 傘(かさ)に着る
- 笠(かさ)に着る
- 嵩(かさ)に着る
第10問「部下は酒が弱いため、ちびりちびりと{飲む・呑む}」、正しいのは、どっち?
- 飲む
- 呑む
8問目は単なるケアレスミス。
/\/(?:\\.|[^\n\\\/])+\/[gim]*(?!\w)/g /\/(?:\\.|[^\n\\\/]+)+\/[gim]*(?!\w)/g // 死ぬほど遅い
どちらも同じく /regexp/i みたいな正規表現リテラルにマッチするのだが、下の方がブラウザが固まるほど遅い*。バックトラックの影響だろうか、これまで気にしてこなかったが……。原因が推測できない(無能)のがイタイ。(この日記、SHJSによるソースコードハイライトを多用していると読み込み完了直前にブラウザの反応がいっとき消えている。正規表現を最適化する余地があるならしたい)
昨日 rubyco(るびこ)の日記 (2007-06-20 正規表現の選択) 経由でウィッシュリストに入れた『詳説 正規表現 第2版』⁑がタイムリーすぎる。
reg = /.*x|a/ s = "a" * 11_000_000 m = reg.match(s)
SHJSの javascript定義ファイル(lang/sh_javascript.js)の元になったファイル(javascript.lang)の中身がこれ。
include "java.lang" subst keyword = "abstract|break|case|catch|class|const|continue|debugger|default|delete|do|else|enum|export|extends|false|final|finally|for|function|goto|if|implements|in|instanceof|interface|native|new|null|private|protected|prototype|public|return|static|super|switch|synchronized|throw|throws|this|transient|true|try|typeof|var|volatile|while|with"
javaて……。キーワードにしても使ったことのないものがいっぱい。
あまりにあんまりなんで一から書いた。(sh_javascript.js, sh_javascript.min.js)。 参照したのは JScript5.5の HTMLHelpなので JScript.NETや ECMAScript4には対応していない。古典的な JavaScript。
ついでに同じものを SakuraEditorにも。(javascript_keywords.zip)
オブジェクトリテラルの存在も JScript5.5の HTMLHelpでの扱いが一行しかなかった*ために気付くのが遅れたが、これはその上をいく。古典的な javascriptで何年ぶりの新発見。しかもこれもちゃんと書いてあった。(但し一行⁑)
block: { break block; }
ループにラベルを付けて、ネストしたループから外側のループを continueしたり breakしたりできるのは知っていた(が忘れていた)が、ラベル付きブロックと breakのこの使い方は全く知らなかった。
labelのスコープはローカル変数と同じみたいで⁂、functionをまたいで goto代わりには使えなかった。
function(){ alert(1) }(); void function(){ alert(2) }(); (function(){ alert(3) })();
alertが表示されるのは 2と 3。二行目ので初めて voidの使い途を見つけたと思ったのだったが三番目の書き方もあるらしく、またそれが一般的っぽい。まあ、なんとなくわかる。あれでしょ。
1行目の functionだけ functionExpressionではなく functionDeclarationだと解釈されて、続く () は関数呼び出しのかっこではなく、式をグルーピングするかっこになるのだとか。(functionDeclarationでは関数名を省略できないのだから、(省略できる)functionExpressionだと解釈してくれても良さそうなものだが)
そういう理由なのでこんなのもあり。
+function(){ alert(4) }(); (function(){ alert(5) }());
絶望した! Hikiに同梱されてる HikiDocのバージョンが tDiaryのより低いのに絶望した! (tDiaryのもたいがい古いのに>20080112p01) (もちろん最新の hikidoc.rbに入れ替えました)
中身ががらっと変わっていてびっくり。浦島太郎になっていた。Rubyist Magazine 出張版で hikidoc.rbが添削されていて、そのコードをベースに書き直したらしい。
tDiary-2.2.0同梱の hikidoc.rbにあった、複数行PREや複数行プラグイン記法が干渉する問題がなくなっている、というのがアップデートの目的。
Wikiスタイルでこういう本文を書くと
!_ <<< {{{}}} >>> {{'test'}}
このような HTMLになっていた ( {{'test'}}は testになっているべき )が、
<h3><a name="p01" href="./20080111.html#p01"><span class="sanchor">_</span></a> _</h3> <pre> {{{}}} </pre> <p>{{'test'}}</p>
VERSION 0.0.2では直っている。
HikiDocのアップデートに伴って、HikiDocを呼び出す部分を下のように変更する必要がある。
def to_html( string ) html = HikiDoc::to_html( string, :level => 3, :use_wiki_name => false, :allow_bracket_inline_image => false, :plugin_syntax => method(:valid_plugin_syntax?) ).strip
本文中にべた書きしたWikiNameをリンクにしてほしくない。
[[beautiful_stuff.png]]など角かっこに入れた画像っぽい URLを <img>に置換してほしくない。
空タグの終了の仕方は HikiDoc.to_htmlと HikiDoc.to_xhtmlを呼び分けることで変更する。
以前の形式のままでも大丈夫なように互換性が保たれているが、警告が出るので。
以前は二重のbrace(=プラグイン記法)など HikiDocにとって意味のある記号をそのまま表示したいときに
\{{plain text}}
とエスケープすることができたがそれが不可能になっている。今は代わりに
{{'{{'}}plain text}}
と書いている。他の書き方もあるかもしれないがプラグイン記法は将来も変更されないだろうから冗長でもこれが確実*。さもないとエスケープ方法が変わったときに過去の日記を書き換えてまわる羽目になる。(今の自分のように)
以前の hikidoc.rbに加えていた変更を新しい方にも。
「記法が有効になる ⇒ これらの文字をそのまま表示するためには一手間必要」
なんだけど、複数行PRE記法(<<<〜>>>)をそのままの状態(一切の修飾が無効)で残しているので必要なら(というか強調や打ち消しが不要なら)そちらを使える。
--- hikidoc.rb.r87 Mon Jan 14 09:43:25 2008 +++ hikidoc.rb Mon Jan 14 09:57:35 2008 @@ -328,11 +328,11 @@ INDENTED_PRE_RE = /\A[ \t]/ + # ''em'', '''strong''', ==strike== and {{plugin}} are available. def compile_indented_pre(f) lines = f.span(INDENTED_PRE_RE)\ - .map {|line| rstrip(line.sub(INDENTED_PRE_RE, "")) }\ - .map {|line| @output.text(line) } - @output.preformatted restore_plugin_block(lines.join("\n")) + .map {|line| rstrip(line.sub(INDENTED_PRE_RE, "")) } + @output.preformatted compile_modifier(lines.join("\n")) end BLOCK_PRE_OPEN_RE = /\A<<<\s*(\w+)?/
強調は有効にするけどプラグインは無効がいいなら、追加分の最終行を
+ @output.preformatted compile_modifier(restore_plugin_block(lines.join("\n")))
こうすればよいが、そうするとモディファイア(''em'', '''strong''', ==strike==)をモディファイアでない、そのままのテキストとして書く方法がなくなる気がする。
* プラグイン記法の中身は HikiDocを利用するアプリケーション(Hikiや tDiaryなど)によって許可される内容が変わってくる。プラグインを利用して書かれた HikiDoc形式の文章は HikiDocを利用するアプリ間でポータブルでないかもしれない。全然確実ではなかった。実際この書き方は Hikiに、有効なプラグインの呼び出しではないとエラーにされてしまう。だってただの文字列だから……。
最終更新: 2016-11-12T11:41+0900
Rubyの、括弧を使った %記法だって。
irb19> re = /%[Qq]?(?<brace>\{[^\{}]*(?:\g<brace>[^\{}]*)*})/ irb19> strings = %w(%{z}a %{a{b}z}c %{a{b}c{d{e}f}z}g %{{{{}}}z}a %{a{b}c %{z}a}b) irb19> strings.each{|str| p str[re] } "%{z}" "%{a{b}z}" "%{a{b}c{d{e}f}z}" "%{{{{}}}z}" nil "%{z}" => ["%{z}a", "%{a{b}z}c", "%{a{b}c{d{e}f}z}g", "%{{{{}}}z}a", "%{a{b}c", "%{z}a}b"]
どの例も正しい範囲( %{ から z} まで)を切り取っているのがわかる。
/%[Qq]?(?<brace>\{(?:[^\{}]++|\g<brace>)*})/
若干速い。同じパターン( [^\{}] )の繰り返しも存在しない。http://fleur.hio.jp/perldoc/perl/5.10.0/pod/perl5100delta.mix.html#Regular_expressions を参考にした。
/%[Qq]?(?<brace>\{(?:[^\{}]+|\g<brace>)*})/
上のものの + が一つ落ちたもの。開き括弧が余分にある文字列を食わせると待てども待てども返ってこない。 http://mlog.euqset.org/archives/ruby-list/42232.html で既に書かれている。それに対する返答が http://blade.nagaokaut.ac.jp/cgi-bin/scat.rb/ruby/ruby-list/42233 。
最終更新: 2016-11-12T11:41+0900
ブラウザの等幅フォントを Consolasにした(20080110p01)だけではあきたらずエディタのフォントも Consolasにしようと思ったが……。
Firefoxは FontLinkを設定しなくても Consolasに足りない文字を他のフォントで補完してくれた。SakuraEditorは化けた。レジストリをいじって FontLinkを有効にして Windowsを再起動しても化けた。でも Unicode版の SakuraEditorなら化けなかった。
Unicode版はまだ SakuraEditorのメインストリームではない(というかバグバグ&レイアウト遅い)が、フォントのために乗り換えた。MSゴシックには戻りたくない。
[HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\Windows NT\CurrentVersion\FontLink\SystemLink] Consolas | REG_MULTI_SZ | M+1VM+IPAG-circle.ttf
http://sourceforge.net/tracker/index.php?func=detail&aid=1832567&group_id=12488&atid=312488
最終更新: 2016-11-12T11:41+0900
テキストエディタは、メモ帳、TeraPadときて、現在は SakuraEditorを、もう五年以上使っている。xyzzy(正式な読み方はないらしい。ジッズィと読んでいる。saxyunの読みがサッキュンなのは詐欺だと思います)や Meadowに移る気は無し。
構文ハイライトには興味がなくて、どこかからダウンロードしてきた強調キーワードファイルや正規表現キーワードファイルを使っていた。間違いがあっても正規表現を直したりはせず、オフにするだけ。邪魔にならなければいい程度の扱いだった。
ところが最近 SHJSをいじってたこともあって中途半端なハイライトが許せなくなってしまった。
そのためには鬼車が必要。BREGEXP.dllでは %Q<a<b<c>d<e>f>g> のように入れ子になった括弧の対応を調べられないので。
キーワードファイルはこの日記の SHJS(with カスタマイズ版 sh_ruby.js)を使ったハイライトとコンパチ。ただし完全移植ではない。サクラエディタの正規表現キーワードは正規表現一発で、マッチした全体を特定の配色にするだけだけど、SHJSはパターン中のキャプチャグループごとに配色を分けることができる。サクラエディタの制限で、正規表現が行をまたいでマッチできないことも影響がある。正規表現マッチが行をまたげないのは SHJSも同じなのだが、SHJSは正規表現キーワードから利用できるスタックを用意していて、行を超えて色指定を継続できる。
Firefoxも IE7も可能な場所はすべてメイリオを使うことにしているが、Serif(明朝)と等幅フォントをメイリオにするわけにはいかない。いままで等幅フォントはデフォルトの MS ゴシックのままにしていたのだが、よく耐えられたものだと思う。日本語部分は MS ゴシックなりなんなりに頼らなければならない*にしても英字部分の選択肢は無数にあるじゃないか、ということに気付いてしまった。
http://www.codestyle.org/css/font-family/sampler-Monospace.shtml
これはもう Bitstream Vera Sans Monoしかない。Nimbus Mono Lも悪くはないけど、Bitstream Vera Sans Monoは線が太めで、MSゴシックみたいに掠れた感じが全くしないのがいい。他のフォントはヒゲが多かったり、四角かったりで好みではない。
Firefoxで Bitstream Vera Sans Monoの文字をどれだけ拡大しても太字と普通の字が区別できないのはフォントの問題じゃない気がしてきた。ノーマル、太字、斜体、太字&斜体の四種がインストールされてるのは確認してるし、フォントビューアでは区別できるのに、Firefoxでは区別できない。
……。Firefoxを再起動したら区別できるようになった。フォントをインストールした後は一回再起動した方がいいみたい。
フォントの選択肢が少なすぎる。Bitstream Vera Sans Monoも設定できなかった。それに日本語のページでのデフォルトのフォントは指定できるのに英語のページでのフォントが指定できないのはなんで?⁑
等幅でも明朝でもゴシックでもない。そもそも Googleの検索ボックスの中の文字があまりに見苦しかったから新しい等幅フォントを探してきたというのに。どのフォントの設定をいじっても変化しない。ユーザースタイルシートでなんとかなるか?
……。userContent.cssに
input { font-family: monospace }
を追加したら Bitstream Vera Sans Monoになった。ヽ(^0^)ノわーい
なんでもかんでも monospaceにしたら場所(幅)をとって仕方ないのでやっぱりちょっと変更。
input { font-family: monospace } input[type] { font-family: sans-serif } input[type=text], input[type=file] { font-family: monospace }
Consolasは Vistaに付いてくる、Bitstream Vera Sans Monoの左右を詰めたようなフォント。空間効率って大事。(自分は気にしないけど、ゼロの形もシータの大文字みたいなのから一般的な斜線の入ったものになっている)
body { padding : 0 2.5% ; font-family : 'Verdana', 'Osaka', 'MS UI Gothic', sans-serif ; font-size : 100% ; }
Verdanaは嫌いじゃないよ、プロポーショナルだから Sakura Editorでは使えないけど。MS UI ゴシックも悪くない。メイリオ以前はこれを常用していた。sans-serif(ゴシック)も serif(明朝)より断然好きだ。
でもフォントは自分で(見る人間が)選ぶ。ファウスト並みにこだわってるのならその意志は尊重する(けど読みにくかったら読まない)けれども。
ファイルはこちら20080101p01。
あいだにコメントを挟みながら一連の DIFF差分(sh_ruby.js.diff)を見ていく。
--- sh_ruby.js.original Fri Aug 3 12:16:32 2007 +++ sh_ruby.js Sat Jan 19 01:35:32 2008 @@ -2,123 +2,427 @@ this.sh_languages = {}; } sh_languages['ruby'] = [ [ { - 'regex': /\b(?:require)\b/g, - 'style': 'sh_preproc' + regex: /\brequire\b/g, + style: 'sh_preproc' },
- { - 'next': 1, - 'regex': /#/g, - 'style': 'sh_comment'
+ { // part of Kernel methods. + regex: /\b(?:exit!?|(?:abort|at_exit|BEGIN|callcc|END|eval|exec|fork|load|spawn|syscall|system|trap|warn)\b)/g, + style: 'sh_preproc' },
{ - 'regex': /\b[+-]?(?:(?:0x[A-Fa-f0-9]+)|(?:(?:[\d]*\.)?[\d]+(?:[eE][+-]?[\d]+)?))u?(?:(?:int(?:8|16|32|64))|L)?\b/g, - 'style': 'sh_number' + regex: /[+-]?\b(?:0(?:x[A-Fa-f0-9_]+|d[\d_]+|b[01_]+|o?[0-7_]+)|(?:0\.)?[\d_]+(?:[Ee][+-]?[\d_]+)?)\b/g, + style: 'sh_number' },
{ - 'next': 2, - 'regex': /"/g, - 'style': 'sh_string' + next: 2, + regex: /"/g, + style: 'sh_string' }, { - 'next': 3, - 'regex': /'/g, - 'style': 'sh_string' + next: 3, + regex: /'/g, + style: 'sh_string' },
{ - 'next': 4, - 'regex': /</g, - 'style': 'sh_string' + next: 4, + regex: /<(?=[\w\/])/g, + style: 'sh_string' },
{ - 'regex': /\/[^\n]*\//g, - 'style': 'sh_regexp' + regex: /\/(?:\\.|[^\n\\\/])*\/[eimnosux]*(?![A-Za-z])/g, + style: 'sh_regexp' },
{ - 'regex': /(%r)(\{(?:\\\}|#\{[A-Za-z0-9]+\}|[^}])*\})/g, - 'style': ['sh_symbol', 'sh_regexp']
+ regex: /(?:\b(?:alias|attr(?:_reader|_writer|_accessor)?|begin|break|case|do|else|elsif|end|ensure|for|if|in|include|lambda|loop|next|proc|raise|redo|rescue|retry|return|super|then|undef|unless|until|when|while|yield|and|not|or|def|class|module|catch|fail|throw)\b|&&|\|\|)/g, + style: 'sh_keyword' }, { - 'regex': /\b(?:alias|begin|BEGIN|break|case|defined|do|else|elsif|end|END|ensure|for|if|in|include|loop|next|raise|redo|rescue|retry|return|super|then|undef|unless|until|when|while|yield|false|nil|self|true|__FILE__|__LINE__|and|not|or|def|class|module|catch|fail|load|throw)\b/g, - 'style': 'sh_keyword'
+ next: 5, + regex: /^=begin/g, + style: 'sh_comment' }, { - 'next': 5, - 'regex': /(?:^\=begin)/g, - 'style': 'sh_comment'
+ regex: /@@?[A-Za-z_][A-Za-z0-9_]*/g, + style: 'sh_type' }, - { - 'regex': /(?:\$[#]?|@@|@)(?:[A-Za-z0-9_]+|'|\"|\/)/g, - 'style': 'sh_type'
+ { // global variables + regex: /\$(?:[_&~`'\+\?!@=\/\\,;\.<>\*\$:"]|-?[A-Za-z0-9_]+\b)/g, + style: 'sh_type' + },
+ { // %r(regexp) + next: 6, + regex: /%r(?=[\(<\[\{])/g, + style: 'sh_regexp' + }, + { // %x(command), %w(array) + next: 11, + regex: /%[xWw](?=[\(<\[\{])/g, + style: 'sh_normal' + }, + { // %(string), %s(symbol) + next: 16, + regex: /%[Qqs]?(?=[\(<\[\{])/g, + style: 'sh_string' + },
+ { // %r!regexp!i + regex: /%r([ -'*-\/:;=?@\\^_`|~])(?:\\.|.)*?\1[eimnosux](?![A-Za-z])/g, + style: 'sh_regexp' + }, + { // %x!command!, %w!array! + regex: /%[xWw]?([ -'*-\/:;=?@\\^_`|~])(?:\\.|.)*?\1/g, + style: 'sh_string' + }, + { // %!string!, %s!symbol! + regex: /%[Qqs]?([ -'*-\/:;=?@\\^_`|~])(?:\\.|.)*?\1/g, + style: 'sh_string' + },
+ { // Symbol + regex: /(:)((?:@@?|\$|[A-Za-z_])\w+\b[!\?]?)/g, + style: ['sh_symbol', 'sh_string'] + }, + { // Symbol + regex: /(:)(\+|~|\*\*?|-|\/|%|<=>|<<?|>>?|^|===?|=~|!~|&|\|)(?=[^\w\d]|$)/g, + style: ['sh_symbol', 'sh_string'] + },
+ { // Constants + regex: /\b[A-Z]\w+\b/g, + style: 'sh_function' + }, + { // Constants + regex: /\b(?:self|nil(?!\?)|true|false|__FILE__|__LINE__)\b/g, + style: 'sh_function' + },
+ { // don't highlight ? and ! as symbols if they are part of a method call + regex: /\b[a-z_]\w*[!\?]/g, + style: 'sh_normal' }, { - 'regex': /[A-Za-z0-9]+(?:\?|!)/g, - 'style': 'sh_normal'
+ regex: /~|!|%|\^|\*|\(|\)|-|\+|=|\[|\]|\\|::?|;|,|\.|\/|\?|&|<|>|\|/g, + style: 'sh_symbol' }, { - 'regex': /~|!|%|\^|\*|\(|\)|-|\+|=|\[|\]|\\|:|;|,|\.|\/|\?|&|<|>|\|/g, - 'style': 'sh_symbol' + regex: /(#)(\{)/g, + style: ['sh_symbol', 'sh_cbracket'] }, { - 'regex': /(#)(\{)/g, - 'style': ['sh_symbol', 'sh_cbracket'] + regex: /\{|\}/g, + style: 'sh_cbracket' }, { - 'regex': /\{|\}/g, - 'style': 'sh_cbracket'
+ next: 1, + regex: /#/g, + style: 'sh_comment' } ],
[ { - 'exit': true, - 'regex': /$/g + exit: true, + regex: /$/g } ], [ { - 'exit': true, - 'regex': /$/g + exit: true, + regex: /$/g }, { - 'regex': /\\(?:\\|")/g + regex: /\\[\\"]/g }, { - 'exit': true, - 'regex': /"/g, - 'style': 'sh_string' + exit: true, + regex: /"/g } ], [ { - 'exit': true, - 'regex': /$/g + exit: true, + regex: /$/g }, { - 'regex': /\\(?:\\|')/g + regex: /\\[\\']/g }, { - 'exit': true, - 'regex': /'/g, - 'style': 'sh_string' + exit: true, + regex: /'/g } ], [ { - 'exit': true, - 'regex': /$/g + exit: true, + regex: /$/g }, { - 'exit': true, - 'regex': />/g, - 'style': 'sh_string' + exit: true, + regex: />/g } ], [ { - 'exit': true, - 'regex': /^(?:\=end)/g, - 'style': 'sh_comment' + exit: true, + regex: /^=end/g } + ],
+ [ // state 6-10: %r(regexp) + { + exit: true, + regex: /$/g + }, + { + next: 7, + regex: /\(/g, + style: 'sh_regexp' + }, + { + next: 8, + regex: /</g, + style: 'sh_regexp' + }, + { + next: 9, + regex: /\[/g, + style: 'sh_regexp' + }, + { + next: 10, + regex: /\{/g, + style: 'sh_regexp' + }, + { + exit: true, + regex: /[)>\]}][eimnosux]*/g, + style: 'sh_regexp' + } + ], + [ + { + exit: true, + regex: /$/g + }, + { + next: 6, + regex: /(?=\()/g + }, + { + exit: true, + regex: /(?=\))/g + } + ], + [ + { + exit: true, + regex: /$/g + }, + { + next: 6, + regex: /(?=<)/g + }, + { + exit: true, + regex: /(?=>)/g + } + ], + [ + { + exit: true, + regex: /$/g + }, + { + next: 6, + regex: /(?=\[)/g + }, + { + exit: true, + regex: /(?=])/g + } + ], + [ + { + exit: true, + regex: /$/g + }, + { + next: 6, + regex: /(?={)/g + }, + { + exit: true, + regex: /(?=})/g + } + ], + [ // state 11-15: %x(command) + { + exit: true, + regex: /$/g + }, + { + next: 12, + regex: /\(/g, + style: 'sh_normal' + }, + { + next: 13, + regex: /</g, + style: 'sh_normal' + }, + { + next: 14, + regex: /\[/g, + style: 'sh_normal' + }, + { + next: 15, + regex: /\{/g, + style: 'sh_normal' + }, + { + exit: true, + regex: /[)>\]}]/g, + style: 'sh_normal' + } + ], + [ + { + exit: true, + regex: /$/g + }, + { + next: 11, + regex: /(?=\()/g + }, + { + exit: true, + regex: /(?=\))/g + } + ], + [ + { + exit: true, + regex: /$/g + }, + { + next: 11, + regex: /(?=<)/g + }, + { + exit: true, + regex: /(?=>)/g + } + ], + [ + { + exit: true, + regex: /$/g + }, + { + next: 11, + regex: /(?=\[)/g + }, + { + exit: true, + regex: /(?=])/g + } + ], + [ + { + exit: true, + regex: /$/g + }, + { + next: 11, + regex: /(?={)/g + }, + { + exit: true, + regex: /(?=})/g + } + ], + [ // state 16-20: %Q(string) + { + exit: true, + regex: /$/g + }, + { + next: 17, + regex: /\(/g, + style: 'sh_string' + }, + { + next: 18, + regex: /</g, + style: 'sh_string' + }, + { + next: 19, + regex: /\[/g, + style: 'sh_string' + }, + { + next: 20, + regex: /\{/g, + style: 'sh_string' + }, + { + exit: true, + regex: /[)>\]}]/g, + style: 'sh_string' + } + ], + [ + { + exit: true, + regex: /$/g + }, + { + next: 16, + regex: /(?=\()/g + }, + { + exit: true, + regex: /(?=\))/g + } + ], + [ + { + exit: true, + regex: /$/g + }, + { + next: 16, + regex: /(?=<)/g + }, + { + exit: true, + regex: /(?=>)/g + } + ], + [ + { + exit: true, + regex: /$/g + }, + { + next: 16, + regex: /(?=\[)/g + }, + { + exit: true, + regex: /(?=])/g + } + ], + [ + { + exit: true, + regex: /$/g + }, + { + next: 16, + regex: /(?={)/g + }, + { + exit: true, + regex: /(?=})/g + } ] ];
SHJSのスクリプトは全て、機能が同じでファイルサイズが違う hoge.jsと hoge.min.jsの二種類が収録されている。言語ごとに定義ファイルが分かれているのもおそらく転送量を抑えるためで、個々の jsファイルのほとんどが数キロバイトに収まっている。
*.min.jsファイルは JSMINというツールで空白を詰めることで作られている。JSMINのオリジナルは DOS実行ファイルだけど、C#、Java、JavaScript、Perl、PHP、Python、OCAML、Rubyの実装もある。javascriptを圧縮するのなら javascriptを使いたいよね、ということで javascriptバージョンの jsmin.jsをダウンロードしてきた。
jsmin.jsの中には jsmin()という関数が一つだけある。これに javascriptのソースを渡すとコンパクトになったソースが返ってくるのだけどどうやって実行しよう。jsmin.jsと同じ場所にあった test.htmlをブラウザで表示してテキストエリアにソースを貼り付けて実行するのもありだが sh_ruby.jsをちょこちょこいじってる身としては毎回となると面倒くさい。
というわけで J(ava)Scriptで exec_jsmin.jsというのを書いた。jsmin.jsと同じ場所に置いたこのファイルに *.jsファイルをドロップすると *.min.jsというファイルを作成する。
var fso = new ActiveXObject("Scripting.FileSystemObject"); function ReadFile(path) { var ts = fso.OpenTextFile(path, 1, false); var text = ts.ReadAll(); ts.Close(); return text; } function WriteFile(path, text) { var ts = fso.CreateTextFile(path, true, false); ts.Write(text); ts.Close(); } eval(ReadFile(fso.BuildPath(fso.GetParentFolderName(WScript.ScriptFullName), "jsmin.js"))); var args = WScript.Arguments; for(var i = 0; i < args.Length; ++i) { var path = args(i); if(fso.FileExists(path)) { var path_min = fso.BuildPath(fso.GetParentFolderName(path), fso.GetBaseName(path)) + '.min.js'; WriteFile(path_min, jsmin(ReadFile(path))); } else { WScript.Echo("FileNotExist:"+path); } }
最初から最後まで J(ava)Scriptで完結して満足です。
ファイルはこちら。20080101p01。
頭の方から変更点を見ていく。
- 'regex': /\b(?:require)\b/g, + 'regex': /\brequire\b/g,
require一つだけだからかっこで囲む必要はない。
- 'regex': /\b(?:defined\?|Array|Floar|Integer|String|abort|callcc|exec|exit!?|fork|proc|lambda|set_trace_func|spawn|syscall|system|trace_var|trap|untrace_var|warn)\b/g, + 'regex': /\b(?:defined\?|exit!?|(?:abort|callcc|exec|fork|set_trace_func|spawn|syscall|system|trace_var|trap|untrace_var|warn)\b)/g,
Array、Floar(Floatのスペルミスでした)、Integer、Stringを取り除いて、定数のルールが適用されるように。sh_preprocではなく sh_functionになる。
lambdaと procも取り除いて、sh_keywordに含めることにした。
\bは defined?の ?と exit!の !の直前にマッチし、?の後や !の後にはマッチしないので正しくマッチするように修正。
- { // Symbol - 'regex': /:(?:(?:@@|@|\$)?\w+[\?!]?|\+=?|!=?|~|\*\*=?|-=?|\*=?|\/=?|%=?|<<=?|>>=?|&=?|\|=?|^=?|>=?|<=?|<=>|===?|=~|!~|&&=?|\|\|=?|\.\.|\.\.\.|=)(?=\s|$)/g, - 'style': 'sh_string' - }, + { // Symbol + 'regex': /(:)((?:@@|@|\$)?\w+\b[!\?]?)/g, + 'style': ['sh_symbol', 'sh_string'] + }, + { // Symbol + 'regex': /(:)(\+|~|\*\*|-|\*|\/|%|<<?|>>?|^|<=>|===?|=~|!~|&|\|)(?=[^\w\d]|$)/g, + 'style': ['sh_symbol', 'sh_string'] + },
あまりにルールが乖離してるので Symbolのルールを分割。加えて、不正な Symbolリテラルをルールから除外(代入、複合代入、:&&、:||、:...など)
リテラルの先頭の : を sh_stringから sh_symbolにしたのは
:"hoge" :hoge
の整合性をとるため。
- 'regex': /\/[^\n]*\//g, + 'regex': /\/(?:\\.|[^\n\\\/])*\/[eimnosux]*(?!\w)/g,
正規表現リテラルのオプション部分もマッチに含めるように。あと条件を厳しくしたので URLに誤マッチすることが減るはず。
- 'regex': /(?:\b(?:alias|begin|BEGIN|at_exit|break|case|do|else|elsif|end|END|ensure|for|if|in|include|loop|next|raise|redo|rescue|retry|return|super|then|undef|unless|until|when|while|yield|and|not|or|def|class|module|catch|fail|load|throw)\b|&&|\|\|)/g, + 'regex': /(?:\b(?:alias|begin|BEGIN|at_exit|break|case|do|else|elsif|end|END|ensure|for|if|in|include|lambda|loop|next|proc|raise|redo|rescue|retry|return|super|then|undef|unless|until|when|while|yield|and|not|or|def|class|module|catch|fail|load|throw)\b|&&|\|\|)/g,
lambdaと procを sh_preprocから sh_keywordへ持ってきた。どちらもメソッドになりうる重要な要素だと思うから。
- 'regex': /\b[A-Z]\w+[!\?]?(?=\b|$)/g, + 'regex': /\b[A-Z]\w+\b[!\?]?/g,
\bを正しく使用。最後の [!\?]?は不要でした。試してみたらエラーになった。
- 'regex': /\b(?:false|nil(?!\?)|true|self|__FILE__|__LINE__)(?=\b|$)/g, + 'regex': /\b(?:false|nil(?!\?)|true|self|__FILE__|__LINE__)\b/g,
- 'regex': /[a-z0-9_]+(?:\?|!)/g, + 'regex': /\b[a-z0-9_]+[!\?]?/g,
末尾が ?や !のメソッドだけを拾い上げたかったのだろうか?ローカル変数っぽいものにもマッチするようにしたけど、どのみち色はつかないので害はない。因みに文字配列リテラル( %w(one two three) )も適切なクラスが見つからなかったので sh_normalにしている。
- 'style': 'sh_string' - 'style': 'sh_string' - 'style': 'sh_string' - 'style': 'sh_commend'
'string'、"string"、<tagname>、=begin〜=endの終了条件部分から styleを取り除く。なくても出力は変わらない。それにしても HTMLタグっぽいものにマッチするルールがあるのはなぜだろう。Web用言語だと思われてるのかな?(<stdio>や <stdlib> のたぐいの可能性もある)。不都合はないので消さないけど。
20080101p01からの続き。正式な sh_ruby.js (私的改訂版)はそちらから。
機能は同じ(はず)なのになぜか全く様子の違う二つのスクリプトができてしまった。こんな感じ。
{ // %r(regexp) 'next': 6, 'regex': /%r(?=[\(<\[\{])/g, 'style': 'sh_regexp' }, { // %x(command), %w(array) 'next': 11, 'regex': /%[xWw](?=[\(<\[\{])/g, 'style': 'sh_normal' }, { // %(string), %s(symbol) 'next': 16, 'regex': /%[Qqs]?(?=[\(<\[\{])/g, 'style': 'sh_string' },
[ // state 6-10: %r(regexp) { 'exit': true, 'regex': /$/g }, { 'next': 7, 'regex': /\(/g, 'style': 'sh_regexp' }, { 'next': 8, 'regex': /</g, 'style': 'sh_regexp' }, { 'next': 9, 'regex': /\[/g, 'style': 'sh_regexp' }, { 'next': 10, 'regex': /\{/g, 'style': 'sh_regexp' }, { 'exit': true, 'regex': /[)>\]}]/g, 'style': 'sh_regexp' } ], [ { 'exit': true, 'regex': /$/g }, { 'next': 6, 'regex': /(?=\()/g, }, { 'exit': true, 'regex': /(?=\))/g, } ], [ { 'exit': true, 'regex': /$/g }, { 'next': 6, 'regex': /(?=<)/g, }, { 'exit': true, 'regex': /(?=>)/g, } ], [ { 'exit': true, 'regex': /$/g }, { 'next': 6, 'regex': /(?=\[)/g, }, { 'exit': true, 'regex': /(?=])/g, } ], [ { 'exit': true, 'regex': /$/g }, { 'next': 6, 'regex': /(?={)/g, }, { 'exit': true, 'regex': /(?=})/g, } ], [ // state 11-15: %x(command) { 'exit': true, 'regex': /$/g }, { 'next': 12, 'regex': /\(/g, 'style': 'sh_normal' }, { 'next': 13, 'regex': /</g, 'style': 'sh_normal' }, { 'next': 14, 'regex': /\[/g, 'style': 'sh_normal' }, { 'next': 15, 'regex': /\{/g, 'style': 'sh_normal' }, { 'exit': true, 'regex': /[)>\]}]/g, 'style': 'sh_normal' } ], [ { 'exit': true, 'regex': /$/g }, { 'next': 11, 'regex': /(?=\()/g, }, { 'exit': true, 'regex': /(?=\))/g, } ], [ { 'exit': true, 'regex': /$/g }, { 'next': 11, 'regex': /(?=<)/g, }, { 'exit': true, 'regex': /(?=>)/g, } ], [ { 'exit': true, 'regex': /$/g }, { 'next': 11, 'regex': /(?=\[)/g, }, { 'exit': true, 'regex': /(?=])/g, } ], [ { 'exit': true, 'regex': /$/g }, { 'next': 11, 'regex': /(?={)/g, }, { 'exit': true, 'regex': /(?=})/g, } ], [ // state 16-20: %Q(string) { 'exit': true, 'regex': /$/g }, { 'next': 17, 'regex': /\(/g, 'style': 'sh_string' }, { 'next': 18, 'regex': /</g, 'style': 'sh_string' }, { 'next': 19, 'regex': /\[/g, 'style': 'sh_string' }, { 'next': 20, 'regex': /\{/g, 'style': 'sh_string' }, { 'exit': true, 'regex': /[)>\]}]/g, 'style': 'sh_string' } ], [ { 'exit': true, 'regex': /$/g }, { 'next': 16, 'regex': /(?=\()/g, }, { 'exit': true, 'regex': /(?=\))/g, } ], [ { 'exit': true, 'regex': /$/g }, { 'next': 16, 'regex': /(?=<)/g, }, { 'exit': true, 'regex': /(?=>)/g, } ], [ { 'exit': true, 'regex': /$/g }, { 'next': 16, 'regex': /(?=\[)/g, }, { 'exit': true, 'regex': /(?=])/g, } ], [ { 'exit': true, 'regex': /$/g }, { 'next': 16, 'regex': /(?={)/g, }, { 'exit': true, 'regex': /(?=})/g, } ]
{ // %r(regexp) 'next': 6, 'regex': /%r(?=[\(<\[\{])/g, 'style': 'sh_regexp' }, { // %x(command), %w(array) 'next': 8, 'regex': /%[xWw](?=[\(<\[\{])/g, 'style': 'sh_normal' }, { // %(string), %s(symbol) 'next': 10, 'regex': /%[Qqs]?(?=[\(<\[\{])/g, 'style': 'sh_string' },
[ { 'exit': true, 'regex': /$/g }, { // from 7. next sibling exists. 'next' : 7, 'regex': /(?:\)[^\(\)]*(?=\()|>[^<>]*(?=<)|][^\[\]]*(?=\[)|}[^\{}]*(?={))/g, 'style': 'sh_regexp' }, { // from 7. no next sibling. 'exit' : true, 'regex': /(?:\)[^\)]*\)|>[^>]*>|][^\]]*]|}[^}]*})/g, }, { // from 0. no nesting parenthesis. 'exit' : true, 'regex': /(?:\([^\()]*\)|<[^<>]*>|\[[^\[\]]*]|\{[^\{}]*})/g, }, { // from 0. nesting parenthesis. 'next' : 7, 'regex': /(?:\([^\()]*(?=\()|<[^<>]*(?=<)|\[[^\[\]]*(?=\[)|\{[^\{}]*(?=\{))/g, 'style': 'sh_regexp' } ], [ { 'exit': true, 'regex': /$/g }, { // from 7. next sibling exists. 'next': 7, 'regex': /(?:\)[^\(\)]*(?=\()|>[^<>]*(?=<)|][^\[\]]*(?=\[)|}[^\{}]*(?={))/g, 'style': 'sh_regexp' }, { // from 7. no next sibling. 'exit': true, 'regex': /(?:\)[^\)]*(?=\))|>[^>]*(?=>)|][^\]]*(?=])|}[^}]*(?=}))/g, }, { // from 6. no nesting parenthesis. 'exit' : true, 'regex': /(?:\([^\()]*(?=\))|<[^<>]*(?=>)|\[[^\[\]]*(?=])|\{[^\{}]*(?=}))/g, }, { // from 6. nesting parenthesis. 'next': 7, 'regex': /(?:\([^\()]*(?=\()|<[^<>]*(?=<)|\[[^\[\]]*(?=\[)|\{[^\{}]*(?=\{))/g, 'style': 'sh_regexp' } ], [ { 'exit': true, 'regex': /$/g }, { // from 9. next sibling exists. 'next' : 9, 'regex': /(?:\)[^\(\)]*(?=\()|>[^<>]*(?=<)|][^\[\]]*(?=\[)|}[^\{}]*(?={))/g, 'style': 'sh_normal' }, { // from 9. no next sibling. 'exit' : true, 'regex': /(?:\)[^\)]*\)|>[^>]*>|][^\]]*]|}[^}]*})/g, }, { // from 0. no nesting parenthesis. 'exit' : true, 'regex': /(?:\([^\()]*\)|<[^<>]*>|\[[^\[\]]*]|\{[^\{}]*})/g, }, { // from 0. nesting parenthesis. 'next' : 9, 'regex': /(?:\([^\()]*(?=\()|<[^<>]*(?=<)|\[[^\[\]]*(?=\[)|\{[^\{}]*(?=\{))/g, 'style': 'sh_normal' } ], [ { 'exit': true, 'regex': /$/g }, { // from 9. next sibling exists. 'next': 9, 'regex': /(?:\)[^\(\)]*(?=\()|>[^<>]*(?=<)|][^\[\]]*(?=\[)|}[^\{}]*(?={))/g, 'style': 'sh_normal' }, { // from 9. no next sibling. 'exit': true, 'regex': /(?:\)[^\)]*(?=\))|>[^>]*(?=>)|][^\]]*(?=])|}[^}]*(?=}))/g, }, { // from 8. no nesting parenthesis. 'exit' : true, 'regex': /(?:\([^\()]*(?=\))|<[^<>]*(?=>)|\[[^\[\]]*(?=])|\{[^\{}]*(?=}))/g, }, { // from 8. nesting parenthesis. 'next': 9, 'regex': /(?:\([^\()]*(?=\()|<[^<>]*(?=<)|\[[^\[\]]*(?=\[)|\{[^\{}]*(?=\{))/g, 'style': 'sh_normal' } ], [ { 'exit': true, 'regex': /$/g }, { // from 11. next sibling exists. 'next' : 11, 'regex': /(?:\)[^\(\)]*(?=\()|>[^<>]*(?=<)|][^\[\]]*(?=\[)|}[^\{}]*(?={))/g, 'style': 'sh_string' }, { // from 11. no next sibling. 'exit' : true, 'regex': /(?:\)[^\)]*\)|>[^>]*>|][^\]]*]|}[^}]*})/g, }, { // from 0. no nesting parenthesis. 'exit' : true, 'regex': /(?:\([^\()]*\)|<[^<>]*>|\[[^\[\]]*]|\{[^\{}]*})/g, }, { // from 0. nesting parenthesis. 'next' : 11, 'regex': /(?:\([^\()]*(?=\()|<[^<>]*(?=<)|\[[^\[\]]*(?=\[)|\{[^\{}]*(?=\{))/g, 'style': 'sh_string' } ], [ { 'exit': true, 'regex': /$/g }, { // from 11. next sibling exists. 'next': 11, 'regex': /(?:\)[^\(\)]*(?=\()|>[^<>]*(?=<)|][^\[\]]*(?=\[)|}[^\{}]*(?={))/g, 'style': 'sh_string' }, { // from 11. no next sibling. 'exit': true, 'regex': /(?:\)[^\)]*(?=\))|>[^>]*(?=>)|][^\]]*(?=])|}[^}]*(?=}))/g, }, { // from 10. no nesting parenthesis. 'exit' : true, 'regex': /(?:\([^\()]*(?=\))|<[^<>]*(?=>)|\[[^\[\]]*(?=])|\{[^\{}]*(?=}))/g, }, { // from 10. nesting parenthesis. 'next': 11, 'regex': /(?:\([^\()]*(?=\()|<[^<>]*(?=<)|\[[^\[\]]*(?=\[)|\{[^\{}]*(?=\{))/g, 'style': 'sh_string' } ]
stateいっぱい版の方が素性がいいのは一目瞭然ですね。(;^_^A アセアセ… 書くのにかかった時間は数分の一から十分の一だし、読み返して理解できるのもそっちだし。
ありえない正規表現の方は SHJSのエンジン部分(sh_main.js)を全く利用していないところに複雑さの原因がありそう。括弧の種類ごとに一つの stateが必要でなおかつそれが×3(=12)という stateいっぱい版の見通しに後込みしてこっちの泥沼にはまりこんでいった感じ。
尚どちらも、似てるけどちょっとだけ違うコードがほとんどの部分を占めている。例えば stateいっぱい版の state7-10、state12-15、state17-20の相違点は
'next': 6, // state7-10 'next': 11, // state12-15 'next': 16, // state17-20
の部分だけ。ここを
'next': 'caller'
と書ければ共通化できるのに……。また、state6、state11、state16の違いは
'style': 'sh_regexp' // state6 'style': 'sh_normal' // state11 'style': 'sh_string' // state16
の部分だけここを
'style': 'inherit'
と書ければ共通化できるのに……。
それなら追加部分のサイズが今のほぼ 1/3になったものを。