中身ががらっと変わっていてびっくり。浦島太郎になっていた。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になったものを。
以下、変更点のリスト。(\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 },
文字列リテラルの終了条件に上のは必要ない、むしろこれがあることで複数行にまたがったリテラルを正しく認識できない、のだけど強力すぎる正規表現は誤認識があったときにソースを最後まで一色に染めてしまう危険性があるのでそのままにしている。ヒアドキュメントに対応しないのも同じ理由。
{ // %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";
やっぱりダメだ〜。
上で出した「こんなのも通る」と「やっぱりダメだ〜」の例が、言葉とは裏腹に「通ってない」と「ちゃんとできてる」状態になってると思う。だとしたら成功。
変更点は20080102p01で。
# for variable interpolation, #{ is not a comment
というコメントを付けて #{}のハイライトルールを定義しているにも関わらず、それが #コメントルール よりも後ろにあるために機能していなかった。#コメントルールを後ろに持ってきて解決。
続きは20080105p01で。