/ 最近 .rdf 追記 設定 本棚

脳log[2008-01-12~]



2008年01月12日 (土)

[tDiary] tDiaryの hikidoc.rbを VERSION 0.0.2(r87)に

中身ががらっと変わっていてびっくり。浦島太郎になっていた。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では直っている。

 tdiary/wiki_style.rb の変更

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
 use_wiki_nameオプション(false)を追加。

本文中にべた書きしたWikiNameをリンクにしてほしくない。

 allow_bracket_inline_imageオプション(false)を追加。

[[beautiful_stuff.png]]など角かっこに入れた画像っぽい URLを <img>に置換してほしくない。

 empty_element_suffixオプションを削除。

空タグの終了の仕方は HikiDoc.to_htmlと HikiDoc.to_xhtmlを呼び分けることで変更する。

 呼び出し方法を HikiDoc.new.to_htmlから HikiDoc.to_htmlに変更。

以前の形式のままでも大丈夫なように互換性が保たれているが、警告が出るので。

 エスケープ方法が変わった(というかなくなった?)

以前は二重のbrace(=プラグイン記法)など HikiDocにとって意味のある記号をそのまま表示したいときに

\{{plain text}}

とエスケープすることができたがそれが不可能になっている。今は代わりに

{{'{{'}}plain text}}

と書いている。他の書き方もあるかもしれないがプラグイン記法は将来も変更されないだろうから冗長でもこれが確実*。さもないとエスケープ方法が変わったときに過去の日記を書き換えてまわる羽目になる。(今の自分のように)

 行単位のPRE記法(空白インデントする方のPRE)で、強調、もっと強調、打ち消しするための変更。ついでにプラグイン記法も有効になる。

以前の 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==)をモディファイアでない、そのままのテキストとして書く方法がなくなる気がする。

 追記@2008-02-10: tDiary-2.2.0.20080119で hikidoc.rbが r87になった

 追記@2008-02-10: tDiary-2.2.0.20080210で 警告がでない呼び出し方法になった

* プラグイン記法の中身は HikiDocを利用するアプリケーション(Hikiや tDiaryなど)によって許可される内容が変わってくる。プラグインを利用して書かれた HikiDoc形式の文章は HikiDocを利用するアプリ間でポータブルでないかもしれない。全然確実ではなかった。実際この書き方は Hikiに、有効なプラグインの呼び出しではないとエラーにされてしまう。だってただの文字列だから……。


2008年01月11日 (金) 文字クラス内で後方参照は使えない。/(")[^\1]*\1/ のようなものは [^1]とも [^"]のときとも違う結果になった。(Ruby1.8, JScript, JavaScript(Fx3rc1)で実験)

最終更新: 2016-11-12T11:41+0900

[正規表現][Ruby] 鬼車すごい。正規表現で再帰ができる。

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} まで)を切り取っているのがわかる。

  1. この機能が使える鬼車がのってる Rubyは 1.9.0。
  2. PCRE(ver 4.x)の (?P<name>...)と(?P>name)が同じものにあたるらしい。へー、そんなものが。javascript(JScript)の正規表現も新しくならんかな
  3. .NETの (?<open>...)と(?<close-open>...)も同じことができるらしいが、正直わからん*
  4. http://www.kt.rim.or.jp/~kbk/regex/regex.html < 正規表現の各種実装の違いがよくわかる。

 追記@2008-05-09: 対応する括弧にマッチする正規表現のヴァリエイション(<くどい表記だな)

/%[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

* http://msdn2.microsoft.com/ja-jp/library/bs2twtah(VS.80).aspx#BalancingGroupDefinitionExample

 \1 (後方参照)が、単純にすでに出現したのと同じ文字列にマッチするパターンなのに対して、任意のパターンを指定できるものだと想像してみる。

 追記@2008-05-09: それでは括弧のネストに対応できない。それは条件分岐の一形態。.NETのものはカウンタの増減を指示する構文。

最終更新: 2016-11-12T11:41+0900

[SakuraEditor] SakuraEditor (ANSI版)は FontLinkが効かないよ

ブラウザの等幅フォントを Consolasにした(20080110p01)だけではあきたらずエディタのフォントも Consolasにしようと思ったが……。

Firefoxは FontLinkを設定しなくても Consolasに足りない文字を他のフォントで補完してくれた。SakuraEditorは化けた。レジストリをいじって FontLinkを有効にして Windowsを再起動しても化けた。でも Unicode版の SakuraEditorなら化けなかった。

Unicode版はまだ SakuraEditorのメインストリームではない(というかバグバグ&レイアウト遅い)が、フォントのために乗り換えた。MSゴシックには戻りたくない。

SakuraEditor (Unicode版)

 追記@2008-03-18: レジストリの設定

[HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\Windows NT\CurrentVersion\FontLink\SystemLink]
Consolas | REG_MULTI_SZ | M+1VM+IPAG-circle.ttf

 追記@2008-06-04: ANSI版の FontLink対応パッチが 2007年11月に投稿されていた

http://sourceforge.net/tracker/index.php?func=detail&aid=1832567&group_id=12488&atid=312488

最終更新: 2016-11-12T11:41+0900

[SakuraEditor][Ruby] SakuraEditorの正規表現キーワードファイル(Ruby用)を作っていた

テキストエディタは、メモ帳、TeraPadときて、現在は SakuraEditorを、もう五年以上使っている。xyzzy(正式な読み方はないらしい。ジッズィと読んでいる。saxyunの読みがサッキュンなのは詐欺だと思います)や Meadowに移る気は無し。

構文ハイライトには興味がなくて、どこかからダウンロードしてきた強調キーワードファイルや正規表現キーワードファイルを使っていた。間違いがあっても正規表現を直したりはせず、オフにするだけ。邪魔にならなければいい程度の扱いだった。

ところが最近 SHJSをいじってたこともあって中途半端なハイライトが許せなくなってしまった。

 SHJS+ブラウザと同じ表示を SakuraEditorでも!

そのためには鬼車が必要。BREGEXP.dllでは %Q<a<b<c>d<e>f>g> のように入れ子になった括弧の対応を調べられないので。

 前提条件
 Rubyキーワードファイル(ruby_keywords.zip)をダウンロードしてエディタでインポート。

キーワードファイルはこの日記の SHJS(with カスタマイズ版 sh_ruby.js)を使ったハイライトとコンパチ。ただし完全移植ではない。サクラエディタの正規表現キーワードは正規表現一発で、マッチした全体を特定の配色にするだけだけど、SHJSはパターン中のキャプチャグループごとに配色を分けることができる。サクラエディタの制限で、正規表現が行をまたいでマッチできないことも影響がある。正規表現マッチが行をまたげないのは SHJSも同じなのだが、SHJSは正規表現キーワードから利用できるスタックを用意していて、行を超えて色指定を継続できる。


2008年01月10日 (木)

[Firefox] Bitstream Vera Sans Mono フォント(等幅)をインストール

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を再起動したら区別できるようになった。フォントをインストールした後は一回再起動した方がいいみたい。

 IE7のフォント設定

フォントの選択肢が少なすぎる。Bitstream Vera Sans Monoも設定できなかった。それに日本語のページでのデフォルトのフォントは指定できるのに英語のページでのフォントが指定できないのはなんで?

 Firefoxの<input type="text">でのフォントって?

等幅でも明朝でもゴシックでもない。そもそも 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にしちゃった

Consolasは Vistaに付いてくる、Bitstream Vera Sans Monoの左右を詰めたようなフォント。空間効率って大事。(自分は気にしないけど、ゼロの形もシータの大文字みたいなのから一般的な斜線の入ったものになっている)

 この日記のテーマ(wall3)が font faceまで指定していた件

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(明朝)より断然好きだ。

でもフォントは自分で(見る人間が)選ぶ。ファウスト並みにこだわってるのならその意志は尊重する(けど読みにくかったら読まない)けれども。

* Font linkingで検索

 わかった。ラテン語基本だ。


2008年01月05日 (土)

[SHJS][javascript]SHJS Ruby定義ファイル(sh_ruby.js) 私的改訂版 まとめ。(2008-01-17 最終更新)

ファイルはこちら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'

 必ず最初に実行される処理(BEGIN)、最後に実行される処理(END、at_exit)、スクリプトを終了する(exit)など無視できない働きを持ったメソッドをハイライト。

+    { // 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'
     },

 \bの後ろの +-は絶対にマッチしないので前に出してマッチするように。

 数値リテラルの定義が cpp,java,pascal,perl,php,prolog,python,sh,sql,tclと共通だったので Ruby専用に。(先頭、末尾や連続するアンダースコアやが許容されているがべつに Rubyインタープリタじゃないので気にしない)

     {
-      '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'
     },

 javascriptのオブジェクトリテラルのプロパティ名部分は引用符を省ける(ので省く)。

     {
-      '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'
     },

 <<メソッドや <<HEREDOCUMENTに誤ってマッチしないように、HTMLタグっぽいものだけをハイライト(そもそもなんで <hoge>を文字列としてハイライトするのかは GNU Source-highlightのみぞ知る)。

     {
-      'next': 4,
-      'regex': /</g,
-      'style': 'sh_string'
+      next: 4,
+      regex: /<(?=[\w\/])/g,
+      style: 'sh_string'
     },

 /regexp/i 型の正規表現リテラルの条件を厳しくして URLに誤ってマッチしないように。またオプション部分もマッチに含めるように。

     {
-      'regex': /\/[^\n]*\//g,
-      'style': 'sh_regexp'
+      regex: /\/(?:\\.|[^\n\\\/])*\/[eimnosux]*(?![A-Za-z])/g,
+      style: 'sh_regexp'
     },

 別途定義したので不完全な %r{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")は現状でも、コロンがsh_symbol、その後ろが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'

 ::hogeを Symbolと誤認しないように、先に ::を sh_symbolにしてしまう。

 プロパティ名部分の引用符を省略

+      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'するときの 'style'指定はなくてもかまわないので削除。

 不要なグループ化とエスケープを削除。(?:\=end)

   [
     {
-      '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
+    }
   ]
 ];

2008年01月03日 (木)

[SHJS][javascript] jsmin.js

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で完結して満足です。

[SHJS][javascript]まだまだいじってます。>SHJS | \bを正しく使用 & わずかに減量

ファイルはこちら。20080101p01

頭の方から変更点を見ていく。

 #includeに相当するもの (sh_preproc)

-      'regex': /\b(?:require)\b/g,
+      'regex': /\brequire\b/g,

require一つだけだからかっこで囲む必要はない。

 使用頻度は低いけど無視できないメソッドたち (sh_preprocを流用)

-      '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!の !の直前にマッチし、?の後や !の後にはマッチしないので正しくマッチするように修正。

 シンボル (sh_string)

-    { // 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

の整合性をとるため。

 正規表現リテラル (/regexp/i、sh_regexp)

-      'regex': /\/[^\n]*\//g,
+      'regex': /\/(?:\\.|[^\n\\\/])*\/[eimnosux]*(?!\w)/g,

正規表現リテラルのオプション部分もマッチに含めるように。あと条件を厳しくしたので URLに誤マッチすることが減るはず。

 制御構造と定義に関わるキーワードやメソッド (sh_keyword)

-      '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へ持ってきた。どちらもメソッドになりうる重要な要素だと思うから。

 定数 (sh_function)

-      '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,

 よくわからないもの (sh_normal)

-      'regex': /[a-z0-9_]+(?:\?|!)/g,
+      'regex': /\b[a-z0-9_]+[!\?]?/g,

末尾が ?や !のメソッドだけを拾い上げたかったのだろうか?ローカル変数っぽいものにもマッチするようにしたけど、どのみち色はつかないので害はない。因みに文字配列リテラル( %w(one two three) )も適切なクラスが見つからなかったので sh_normalにしている。

 文字列リテラルとタグとコメント(=begin〜=end)

-      'style': 'sh_string'
-      'style': 'sh_string'
-      'style': 'sh_string'
-      'style': 'sh_commend'

'string'、"string"、<tagname>、=begin〜=endの終了条件部分から styleを取り除く。なくても出力は変わらない。それにしても HTMLタグっぽいものにマッチするルールがあるのはなぜだろう。Web用言語だと思われてるのかな?(<stdio>や <stdlib> のたぐいの可能性もある)。不都合はないので消さないけど。


2008年01月02日 (水)

[SHJS][tDiary][javascript] SHJSの Rubyルールを %[〜]に対応

20080101p01からの続き。正式な sh_ruby.js (私的改訂版)はそちらから。

機能は同じ(はず)なのになぜか全く様子の違う二つのスクリプトができてしまった。こんな感じ。

 Rubyの %記法対応 (stateいっぱい版)

    { // %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,
    }
  ]

 Rubyの %記法対応 (ありえない正規表現版)

    { // %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になったものを。


2008年01月01日 (火) Application Dataなんてフォルダを掘って一人で中に収まってる Operaは恥を知れ (Vistaでの話)

[SHJS][Ruby][tDiary] SHJSの Rubyルールを修正。(sh_ruby.js, sh_ruby.min.js)

以下、変更点のリスト。(\bの使い方が適当なのでスペースの少ないソースで問題が出る可能性あり。\bの使いどころが全然わかってないせい)

    { // part of Kernel methods.
      '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,
      'style': 'sh_preproc'
    },

なくてもいいかな、と思うけど defined?と Kernelモジュールのメソッドの一部を sh_preprocとして追加。Rubyで sh_preprocなのは requireだけなので sh_preprocの配色を流用した。選んだのは abort、callcc、exit、fork、systemなど比較的重要そうなもの。(loopなど一部の他のメソッドは sh_keywordとして既に分類されている)

    {
      'next': 4,
      'regex': /<(?=[\w\/])/g,
      'style': 'sh_string'
    },

正規表現を /</g から変更。<<メソッドやヒアドキュメント(<<HOGE)にマッチしないように。

    { // Symbol
      'regex': /:(?:(?:@@|@|\$)?\w+[\?!]?|\+=?|!=?|~|\*\*=?|-=?|\*=?|\/=?|%=?|<<=?|>>=?|&=?|\|=?|^=?|>=?|<=?|<=>|===?|=~|!~|&&=?|\|\|=?|\.\.|\.\.\.|=)(?=\s|$)/g,
      'style': 'sh_string'
    },

新ルール。シンボル(:hoge)を sh_stringとして色付け。

    { // %!string!
      'regex': /%[Qq]?([!-'*-\/:;=?^]).*?\1/g,
      'style': 'sh_string'
    },

新ルール。%!string!、%Q!string!、%q!string!を sh_stringとして色付け。残念ながら %Q[]のように括弧を使ったものは入れ子になった括弧を数えられないので非対応。対応した。詳しくは下の方。

    {
      '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,
      'style': 'sh_keyword'
    },

ここにはプログラムの流れや定義に関するキーワードや Kernelメソッドが集められているようなので、既に登録されている ENDと同じ働きの at_exitを追加し、definedを削除(上で sh_preprocとして defined?を登録済み)、false、nil、self、true、__FILE__、__LINE__を削除し、あとで定数として定義。&& と || を and、orに対応するものとして追加。

    { // global variables
      'regex': /\$(?:[_&~`'\+\?!@=\/\\,;\.<>\*\$:"]|-?[A-Za-z0-9_]+)/g,
      'style': 'sh_type'
    },

グローバル変数の定義を追加。sh_typeはインスタンス変数やクラス変数のクラス名として使用されているもの。

    { // Constants
      'regex': /\b[A-Z]\w+[!\?]?(?=\b|$)/g,
      'style': 'sh_function'
    },
    { // Constants
      'regex': /\b(?:false|nil(?!\?)|true|self|__FILE__|__LINE__)(?=\b|$)/g,
      'style': 'sh_function'
    },

定数のルールを追加。sh_functionは Rubyでは使われていないクラス。

    {
      'regex': /[a-z0-9_]+(?:\?|!)/g,
      'style': 'sh_normal'
    },

正規表現を /[A-Za-z0-9_]+(?:\?|!)/g から変更。定数は区別したいじゃない。

 余談

    {
      'exit': true,
      'regex': /$/g
    },

文字列リテラルの終了条件に上のは必要ない、むしろこれがあることで複数行にまたがったリテラルを正しく認識できない、のだけど強力すぎる正規表現は誤認識があったときにソースを最後まで一色に染めてしまう危険性があるのでそのままにしている。ヒアドキュメントに対応しないのも同じ理由。

 追記@2008-01-02:括弧を使ったリテラルにも対応した

    { // %r(regexp)
      'next': 6,
      'regex': /%r[\(<\[\{]/g,
      'style': 'sh_regexp'
    },
    { // %x(command), %w(array)
      'next': 7,
      'regex': /%[xWw][\(<\[\{]/g,
      'style': 'sh_normal'
    },
    { // %(string)
      'next': 8,
      'regex': /%[Qq]?[\(<\[\{]/g,
      'style': 'sh_string'
    },
  [
    {
      'exit': true,
      'regex': /$/g
    },
    {
      'next': 6,
      'regex': /[\(<\[\{]/g,
      'style': 'sh_regexp'
    },
    {
      'exit': true,
      'regex': /[)>\]}]/g
    }
  ],
  [
    {
      'exit': true,
      'regex': /$/g
    },
    {
      'next': 7,
      'regex': /[\(<\[\{]/g,
      'style': 'sh_normal'
    },
    {
      'exit': true,
      'regex': /[)>\]}]/g
    }
  ],
  [
    {
      'exit': true,
      'regex': /$/g
    },
    {
      'next': 8,
      'regex': /[\(<\[\{]/g,
      'style': 'sh_string'
    },
    {
      'exit': true,
      'regex': /[)>\]}]/g
    }
  ],

括弧の対応をチェックすることはするけどカッコの種類を区別しないので

%(foo{bar)baz}

こんなのも通る。でも現実的には区別する必要ないよね。HTML断片を組み立てるときに問題がありそう。そしてそういうときにこそダブルクォーテーションを使わずに %[]を使うんだよね。試してみる。

html << %[<option value="#{h hoge}"] << (selected? ? ' selected="selected">' : '>') << h(hoge) << "</option>\n";

やっぱりダメだ〜。

 追記@2008-01-02:括弧を使ったリテラルに正式に対応した

上で出した「こんなのも通る」と「やっぱりダメだ〜」の例が、言葉とは裏腹に「通ってない」と「ちゃんとできてる」状態になってると思う。だとしたら成功。

変更点は20080102p01で。

 追記@2008-01-05:#コメントと #{interpolation}の順番を入れ替え

# for variable interpolation, #{ is not a comment

というコメントを付けて #{}のハイライトルールを定義しているにも関わらず、それが #コメントルール よりも後ろにあるために機能していなかった。#コメントルールを後ろに持ってきて解決。

続きは20080105p01で。