最終更新: 2019-12-01T02:10+0900
20090525に書いたことをやってみた。sh_ruby.jsは巨大なのでとりあえず 0.6/sh_main.js と 0.6/lang/sh_javascript.js (lang/sh_javascript.js) だけ。どちらの sh_javascript.jsも、バックトラックのないノーマル sh_main.jsと組み合わせることもできるつもり(2種類の langファイルをメンテナンスするのは面倒くさいもんね)。0.6/sh_main.jsが自分でも見直したくないソースになっているのは否定できない(だからおおっぴらにリンクしていない)。そういう人間はこう言うのです。「動いたもんが勝ち」
"正しい\ 文字列(ECMAScript 5th ed.の場合)"
"不正な 文字列(ですらない)"
/* 正しい コメント */
/* 閉じていない コメント?
ノーマル、バージョン 0.6の SHJSと違うのは、一つの正規表現で始まりから終わりまでマッチングできない対象(複数行にまたがるものとか)に対して、最初の文字「"」や「/*」を見て、文字列だ、コメントだ、と決めてかかった結果を後で修正できること。
「/*」があった。ここからコメントらしい。「*/」が見つからない。やっぱりコメントではない。という判断が改行をまたいでできる。
NOTE 文字 LineTerminator は、それにバックスラッシュ \ を先行させても、文字列リテラル内には出現できない。
以前、どっちなんだろう?と調べたときに「\(改行)」は空文字列と解釈されるシーケンスだ、と書かれてるの(そういうエスケープシーケンスの一覧表)を見たんだけど実際は違ったらしい。
一次情報にあたらなければ同じミスをおかすんじゃないかと手許にあった「Final final final final draft Standard ECMA-262 5th edition」というタイトルの pdfを開いてみた。(3rd ed.ではなく 5th ed.だということに注意)
A line terminator cannot occur within any token except a StringLiteral. Line terminators may only occur within a StringLiteral token as part of a LineContinuation.
NOTE A line terminator character cannot appear in a string literal, except as part of a LineContinuation to produce the empty character sequence. The correct way to cause a line terminator character to be part of the String value of a string literal is to use an escape sequence such as \n or \u000A.
日本語にもそういう罠があるけど、英語だとうっかり正反対の意味にとってしまうことがあって恐い。それでも、行継続の意味に \(改行)を使う(それは空文字列として解釈される)ことは exception(例外)として認められているように読める。
3rdから 5thに更新するにあたり現状を追認するように書き換わったのかもしれないけど……ECMAScript3の仕様はどこ?
面倒くさがらずにダウンロードしてきた。
NOTE A 'LineTerminator' character cannot appear in a string literal, even if preceded by a backslash \. The correct way to cause a line terminator character to be part of the string value of a string literal is to use an escape sequence such as \n or \u000A.
5thで exceptだった部分に even ifが使われている。現バージョンでは文字列リテラル内での「\(改行)」の使用は明確に否定されてた。
というわけで上のサンプルソースの記述を正しくなるように変更しました。
最終更新: 2019-12-01T02:10+0900
既読は 8/100作品でした。しょぼっ。まあ、SFというラベルは読む理由にも読まない理由にもなってないからね。少ないからコピるのも余裕。
マイクル クライトン『アンドロメダ・ストレイン』も 100冊の中にまぜてあげて欲しかった。
最終更新: 2019-12-01T02:10+0900
行単位で処理してるのはどちらも同じだし、サクラエディタの既存の設定はすべて State0のパターンとして扱えばいい。行をまたぎたいときや凝った処理がしたくなって初めて State移動を使えばいいだけのこと。patternStackと currentStyleを行ごとにキャッシュしておけば、変更のあった行から即座に色分けを再開できるはず。文書の最初から色分けをやり直す必要はない。変更のあった行より後ろの行にジャンプするときは、変更によって何行後ろまでの patternStackと currentStyleに変化を与えたか、というキャッシュが無効化される範囲を見極める時間が必要になりそう。*.cファイルの編集中に /*の *を削除したりアンドゥしたりしたあとで Ctrl+Endするのは厳しいですよ、と。patternStackは一行一行でそうそう変化するもんじゃないからキャッシュの圧縮が効きやすいだろう。(妄想するだけなら気楽だなあ)
色分けが正規表現キーワードだけによって行われるのでないところが難しい。他の色分け機能(コメントとか文字列とか強調キーワードとか)による先食いを考えないといけないから。
今のサクラエディタで、正規表現キーワードで引用符を食べるとその行では組み込みのクォーテーション文字列の色分けが無効になるけど、次の行からクォーテーション文字列が始まってしまう理由がわかった気がする。その逆をやろうとしていたことに今やっと気付いた。正規表現キーワードも先食いされる可能性を考えないといけない。
ショック! BREGEXPのインターフェイスには検索開始位置を指定するパラメータ ――JavaScriptでの /pattern/g.lastIndex―― がない。対象文字列を指すポインタを進めればそこ(文字列途中)が開始位置だ、と言ってよいものではない。それでは ^ のようなアンカーが毎回マッチしてしまうだろうから(試したわけではない)。むむむ。BREGEXPには引退してもらいたい……。サクラエディタは自前で特定のパターン(行頭・行末アンカー、先読み)の有無を、検索パターンに対するパターンマッチングでチェックしてますね。怪我の元に思えるので避けたい方法だけど、これまで信じられないぐらいうまく動いてるのは事実(でも半信半疑)<追記>bregonig.dllや Bregexp.dll for SAKURAを使用しているときは BMatchEx(これは検索開始位置を示すポインタが引数の1つに加えられている)が使用されていたのだった。道理でソースを見ながらいじわるなパターンを与えても正しく動いたはずだ。ちなみに鬼車の APIではさらに検索終了位置を指すポインタを、対象文字列末尾を指すポインタとは別に渡せる。</追記>年代が違うから比較は酷だけど、Javaだか .NETだかの正規表現では渡した対象文字列の、(あるかもしれない)続き次第でマッチ結果が変わってくる可能性をさえ知ることができたはず(よく知らない。そこまでするか、と驚いた記憶だけがある)Javaだった。
正規表現リテラルの影響があるのも嫌だな。パターンとフラグをスラッシュで連結せずに別の引数に分けたら、パターンの中のスラッシュをエスケープする必要がなくなるのに。preg_matchを思い出した。文字列で正規表現リテラルを表現しようだなんて(笑止)。リテラルである(=余分なエスケープが必要ない)ことに価値があるのに、引用符とスラッシュで二重にパターンを囲ませる意図がわからない。
<追記@2009-09-23>サクラエディタのソースにエスケープを付加する処理が見あたらないなと思ったら、スラッシュでなく 0xFFFF をデリミタに使用していた。なるほど。パターン中にそんな値は出てこないだろうな(そんな気がするだけ)。0xFEFFや 0xFFFEは BOMだけど、0xFFFFは空いてるのかな。</追記>
CColor_RegexKeywordだけをいじるんでなく、CColorStrategy関連を作り直して CColor_*をそれに対応させるのがゴール(だということに気がついた)。CColor_Quoteに対する特別扱いをレイアウト部から切り離して SColorStrategyInfoに内包させる、みたいな。SColorStrategyInfoは CColorStrategyのドライバという位置づけ。
でもやっぱり色分けにレイアウトがからんでくるのがわからない。SColorStrategyInfoのメンバの
//参照 CEditView* pcView; CGraphics gr; //(SColorInfoでは未使用) //描画位置 DispPos* pDispPos; DispPos sDispPosBegin; CLogicInt GetPosInLayout() const; const CDocLine* GetDocLine() const; const CLayout* GetLayout() const;
こういうの。
与えられた行に対して、これからはそれより前の行の色分け結果も参照しつつ、一行分の色分け結果をまとめて返すから、描画はそちら(CEditView)でどうぞ、検索語のハイライトとの重ね合わせもそちらでよろしく、といいたいんだけど。
CColor_* って 1つのインスタンスを 1つのプロセス(=1つのドキュメント)で使い回してるのに、(1つの行、直前のメンバ関数の呼び出し、に依存した)状態を持ってるってのが嫌。strtok()並に嫌い。そういうのも SColorStrategyInfoに移したい。
BREGEXPの情報は「BREGEXP DLL」を見てたんだけど、そこには載ってない BMatchEx()というものもあるみたい(どこにあるのかは知らない)(BSubstEx()もあった)。サクラのソース(CBregexpDll2.h)でみつけた関数の型はこう。
typedef int (__cdecl *BREGEXP_BMatchExW2) (const wchar_t* str, const wchar_t* targetbeg, const wchar_t* target, const wchar_t* targetendp, BREGEXP_W** rxp, wchar_t* msg); typedef int (__cdecl *BREGEXP_BMatchW2) (const wchar_t* str, const wchar_t* target, const wchar_t* targetendp, BREGEXP_W** rxp, wchar_t* msg);
これは期待できるなあ。
ざっと検索して一か所でだけ BMatchEX()が使われていた。CBregexp.cppのこの部分
// 拡張関数がない場合は、行の先頭("^")の検索時の特別処理 by かろと if (!ExistBMatchEx()) { /* ** 行頭(^)とマッチするのは、nStart=0の時だけなので、それ以外は false */ if( (m_ePatType & PAT_TOP) != 0 && nStart != 0 ) { // nStart!=0でも、BMatch()にとっては行頭になるので、ここでfalseにする必要がある return false; } // 検索文字列=NULLを指定すると前回と同一の文字列と見なされる matched = BMatch( NULL, target + nStart, target + len, &m_pRegExp, m_szMsg ); } else { // 検索文字列=NULLを指定すると前回と同一の文字列と見なされる matched = BMatchEx( NULL, target, target + nStart, target + len, &m_pRegExp, m_szMsg ); }
「拡張関数がない場合」もあるんですか……。BMatchExの google検索結果もたった 7件だなあ。UNICODE版の正規表現DLLは bregonig.dllしかないから BMatchEx()が存在するものとして BMatch()対策はいらないな。
BMatch()の戻り値は int。だが boolean扱いしてはいけない。BMatch関数のサンプルとして
while (BMatch(patern1,t1+pos,t1+lstrlen(t1),&rxp,msg)) { (マッチング結果の処理) }
みたいに書いてあるので騙されたけど、サクラエディタの CBregexp.cppにはこうある。
// 検索文字列=NULLを指定すると前回と同一の文字列と見なされる matched = BMatchEx( NULL, target, target + nStart, target + len, &m_pRegExp, m_szMsg );
if ( matched < 0 || m_szMsg[0] ) { // BMatchエラー // エラー処理をしていなかったので、nStart>=lenのような場合に、マッチ扱いになり // 無限置換等の不具合になっていた 2003.05.03 by かろと return false; } else if ( matched == 0 ) { // 一致しなかった return false; }
負数の時はエラーらしい。なんだこの落とし穴。そしてパターン文字列を何度も渡さなくてよいという新事実。struct BREGEXP.parapにパターン文字列が保持されてるから 2回目以降の呼び出しでは 2つのパターン文字列(異なっているかもしれない!)を渡すことになるのが気持ち悪かったのだ。ひょっとしたら 2つのパターン文字列を比較して、違っていたら再度コンパイル、みたいな処理があるかもしれないじゃない(そんな処理があってもなくても嫌だ)。
レイアウトと色分けの関係。変更のあった行を知ろうと思うと CDocEditAgentを通って CLayoutMgrへたどり着いてしまうような……。CDocEditAgentのくれる情報はすごく役に立つんだけどエディタ上のテキストの変更がすべてここを通過するんだろうか? CLayoutMgr::InsertData_CLayoutMgr()と LayoutMgr::DeleteData_CLayoutMgr()の二カ所で行の変更を通知するようにした。(後で Replaceも入れて三カ所) <追記@2009-10-14>Deleteは使われてなくて、Insertと Replaceだけで済ませてるみたい。だったら Insertもいらないじゃんねえ。</追記>
次の行以降に影響する文字( " など)を入力しても即座には反映されない(最小化して元に戻すと反映されている。アンドゥ、リドゥすると反映されている)。再描画を行う範囲が最適化されているから、次の行以降の色分けが変わっていて再描画が必要なことを知らせる必要があるみたい。どうすれば……?
before
bool CEditView::DrawLogicLine( HDC _hdc, //!< [in] 作画対象 DispPos* _pDispPos, //!< [in/out] 描画する箇所、描画元ソース CLayoutInt nLineTo //!< [in] 作画終了するレイアウト行番号
after
bool CEditView::DrawLogicLine( HDC _hdc, //!< [in] 作画対象 DispPos* _pDispPos, //!< [in/out] 描画する箇所、描画元ソース CLayoutInt* pnLineTo //!< [in/out] 作画終了するレイアウト行番号
第3引数を双方向にして CEditView::DrawLogicLine()の呼び出し元にさらなる DrawLogicLine()呼び出しと描画領域の拡大をしてもらうことになった。
……ということをやってもらうために SLayoutWork.bNeedChangeCOMMENTMODEや SLayoutWork.pnExtInsLineNumが用意されてたみたい。どうしたもんかな。
やっぱりきたよ分割ビュー問題。表示する行の若い順に再描画すればいいかと思ったけど、通ると思った実行パスが使われていない。別方面で解決。Ctrl+Endでいきなり文書末に飛んだときなんかも最初はエラーだった。
あとは
折り返し、WSHマクロ、矩形選択、置換などはまだ試してない。落ちてから対応する。(分割ビューと Ctrl+Endは乗り越えたけど、折り返しも怖いなあ)
自分が忘れてたので覚え書き。この変更で
正規表現キーワードで複数行にわたる色分けが可能になる。
設定のフォーマットが悩ましい。RxKey[000], RxKey[001],...では表現できないし、ついでにキャプチャグループを利用した、一つのパターン内での色分けを可能にしてもいい。GUIはあきらめてるから SHJSの lang/sh_*.jsを直接読んでやろうか。JSONフォーマット*とかいって……。
正規表現キーワードと組み込みの色分け機能が互いに文字の先食いをできる。
たとえば、正規表現キーワードが組み込み機能より先に引用符を色分け対象にしたとき、次の行から組み込みの色分けが始まることがない > サクラエディタBBS[r7015]。
再帰的な構造に対しては、JavaScriptで動く SHJSに対して鬼車が使えるサクラエディタはもともと後れをとってなかったので、SHJS方式になったところでとくに変化なし。
ときどき落ちる(落ちないはずがない)
レアな機能を呼び出したときが危ない。
JavaScriptで使っていた正規表現パターンを鬼車で動かすにあたっては、[文字集合] の中の [ をエスケープしなければならない、のかな? それだけ?
自分に対しても仕様を明確にしたい。もはや色分け結果は、良くも悪くも以前のものと変わらない。
ファイルIO 面倒くさい。それは経験値がゼロだからだ、ということに気付いた。他人の力を借りよう。JSON_checkerなんて使いやすそうだけど JSONフォーマットには正規表現リテラルもコメントも存在しないのでそのまま使えないのが問題。shjs/lang/sh_*.jsを JSONフォーマットに予め変換しておくのが簡単だけど……(SHJSの言語ファイルをそのまま放り込める、というのがやりたいんだよなあ)。
JSON_checkerを修正して使用することにした。修正内容は……
> allow_comment_and_regex_literal_and_single_quotation_string_and_identifier_as_a_object_key
遷移表が 30×31(=930エントリ)から 40×35(=1400エントリ)へ 50%増加している。ま、気にすることではないな。
enum modesと enum statesをヘッダへ移動させたら、あとは文字を食わせながら JSON_checkerの modeと stateを参照して情報を取り出すだけ。(解釈の段階でバグが発覚しないことを祈る)
(どうでもいいこと: JSON_checker.cの enum定義部分だけが LFでなく CRLF改行だった)
「SHJSの言語ファイルをそのまま放り込める、というのがやりたい」<< 色指定が互換でないから土台無理だった。
sh_preprocとか sh_functionという色指定をいちいち正規表現キーワードN という色指定にマッピングすればいいのかな。sh_string を SQTにするか WQTにするかは決められないけど。
オブジェクトとか配列とかその要素を取得できるようになった。Cに耐えられず、ついでに C++化。ところが、JSON_checkerが最初からガチガチに隙のない C++ライブラリだったら敬遠して手を出さなかったんだよね。他人の C++コードは読めない!
enumの要素(ラベル)が定数に置き換えられて構文エラー。これだからマクロは!!! ヘッダの中だから勝手に #undefもできない。どうすれば……。
#undef IN した。これは windows.hで定義されていたもの。
でけた。GUIがないから iniファイルのディレクトリに rkw2/タイプ設定名.rkw2というファイルが存在すれば勝手に読み込んで色分けする。SHJSの言語ファイルをそのまま放り込むのは無理で、JSONっぽく仕立てないといけない。つまり、ifと代入部分(セミコロンも!)を削除して、オブジェクト配列の配列だけにする必要がある。SHJSの言語ファイルは 0.5と 0.6の間でフォーマットが変わってるけど、それはたぶん効率上の理由だけだから、可読性の高い 0.5のフォーマットにだけ対応。色指定は SQT、RK1というのと、強調キーワード1、正規表現キーワード1というのの両方に対応したつもり(日本語での指定は確認していないした)。SHJSの色指定は sh_normal、sh_keyword、sh_string、sh_comment、sh_number、sh_urlだけがサクラエディタの色指定にマッピングできた。課題は正規表現パターンで、文字集合の中のエスケープされていない [ 。これは BREGEXP.DLLと bregonig.dllの橋渡しをするために色分けと関係なく対応しないといけない。
BREGEXP.DLLしかないときだったら色分け能力が飛躍的に向上した、と言えるんだけど bregonig.dllができる子なので、行またぎぐらいしか色分け能力的には変わりがない。行またぎにしたって誤爆恐さで積極的に使いたいものではないし……。
C/C++とか PL/SQLというタイプ設定名をどうしよう。全角に変換しようか。……。した。<<< 曖昧な書き方だった。タイプ設定名を全角にしたのではなく、読み込むファイル名を全角のものにした、という意味。
ヘッダの減量中。ヘッダは公共の場、最小が美徳。実装は、何でもインクルード、定義して目的のために邁進する、俺の世界。というイメージ。
正規表現キーワードN という色名がわかりにくいのと、rkw2の色指定をポータブルにするために(強調キーワードNや 正規表現キーワードNという色は各人各様に役割が定められていてポータビリティがない)、正規表現キーワード用の色を増やした。
短い名前 | EColorIndexType | 長い名前 |
---|---|---|
RKB | COLORIDX_REGEX_KEYWORD | 正規表現キーワード(予約語) |
RKC | COLORIDX_REGEX_TYPE | 正規表現キーワード(型) |
RKD | COLORIDX_REGEX_VARIABLE | 正規表現キーワード(変数) |
RKE | COLORIDX_REGEX_CONSTANT | 正規表現キーワード(定数) |
RKF | COLORIDX_REGEX_ASSIGN | 正規表現キーワード(代入) |
RKG | COLORIDX_REGEX_OPERATOR | 正規表現キーワード(演算子) |
RKH | COLORIDX_REGEX_FUNCTION | 正規表現キーワード(関数) |
RKI | COLORIDX_REGEX_OBJECT | 正規表現キーワード(オブジェクト) |
RKJ | COLORIDX_REGEX_BLOCK | 正規表現キーワード(構造単位) |
RKK | COLORIDX_REGEX_RXPATTERN | 正規表現キーワード(正規表現パターン) |
RKL | COLORIDX_REGEX_DATE | 正規表現キーワード(日付) |
RKM | COLORIDX_REGEX_TIME | 正規表現キーワード(時刻) |
どなたかの受け売りで代入( = など)と演算子( == など)を分けた。この日記での色分けも以前からそうしている。
タイプ別設定のデフォルトに JavaScriptと Rubyがない!!! PHPも Pythonもないけどね。設定17、設定18、...、設定30という固定長のユーザー設定用プレースホルダもださい。exe一つのポータビリティだけでなく iniファイル一つにもこだわっているのでなければ、iniファイルのディレクトリにサブディレクトリを掘りたい。ディレクトリが空だったり存在しなければ作成してデフォルトを充填する、というポリシーで。
付け加えるなら、コピーされる基本設定とは基本設定のデフォルトではなく、現在の基本設定を優先したい。
カスタマイズされたタイプ設定というのも、すべての項目を持つのではなく、基本設定からの差分、ここだけは譲れないという設定だけを持つようにしたい。強調キーワード、コメント形式、タブ幅、とかそういうのだけ。大部分を基本設定から引き継ぐようにすればカスタマイズの手間が省ける。タイプ別の iniファイルにも、基本設定と異なる設定のみを記録するようにすればいつまでも基本設定を引き継げる。
さらに、強調キーワードをタイプ別設定に移して、ファイルで管理したい。
キーワードの名前、大文字小文字を区別するかどうかのフラグ、と、あとは単語の羅列だけの内容なのでテキストエディタで強調キーワードの管理ができるようになる。sakuraW.iniのスリム化にも役立つ。フォント設定もタイプ別でいいよね。<< やっぱり、プロポーショナルフォントが選べないうちはフォント設定が一つだけでもいいや。
スリム化といえば、MRU関連を別ファイルに保存したい。共通設定、タイプ別設定、フォント設定などと MRUなど履歴情報は更新頻度が違いすぎる。ファイルを分ければ sakuraW.iniの不意の破損を防ぐことにもつながる。設定画面を呼び出して設定を変更しない限り、sakuraW.iniが読み込み専用でも運用できるようになればいい。(ツールバーやステータスバーの表示・非表示設定は微妙だけど sakuraW.ini入りかなあ)
全く関係ないが思い出したこと。既存のファイルを名前を付けて別名で保存したときだったかに、カレントディレクトリがあわせて移動しなくて、外部コマンドの実行で不都合があった。外部コマンドの産物が期待したところ(編集ファイルの位置)に作成されなくて見つけられなかった。
@2009-12-16 カレントディレクトリの件がこの修正で直ったんじゃないかな。>1073
一つだけ棚上げされていた CColor_Foundの移植完了。検索語のハイライトがドキュメントではなくビューに属していたとは知らなかった<追記>あらら不具合扱いだ>画面分割でハイライトが引き継がれない</追記>。棚上げの理由はそれとは関係なくて、検索語の開始位置と終了位置をどこに保存するか、というもの。ともあれ、これで既存の色分けロジックを完全に置き換えることができた。
重さとか、気になるなあ。気がつけば CPUや GPUよりケースやクーラーにお金をかけていた今の PCだけど、腐っても PhenomⅡ720BEだから。Atomの載ったマシンなんて持ってないし。(フルHDとかいうわけでは全くない)DivX動画の再生にも苦労した K6-2でどうだろうか。(ちなみに PhenomⅡは K10)
「ヘッダファイル only (Kazuho@Cybozu Labs: 今更 C++ で JSON パーサ「picojson」を書いたわけ)」というのは考えたことのない*6価値基準だ。そのこころは?
移植ミスかと思って ANSI版 1.6.5.0をダウンロードして試したがどうもそうではない。
\w{10} というパターンで検索をする。F3を押す度に次の 10文字に選択範囲が移動する。Ctrl+F3を押してハイライトを解除する。と、検索語が最後に選択されていた 10文字そのものになってしまうのだ。検索オプションも変わってる。もう一度 F3を押しても、Ctrl+F3を押しても元の状態には戻らない。これは正規表現を使わない検索では問題にならないが、パターン検索では困る。Ctrl+F3を押しただけで検索条件が変わっては困る。ちなみに、「カーソル位置の文字列をデフォルトの検索文字列にする」設定とは関係がない(OFFだから)。
こうしてみた。
//検索マークの切替え // 2001.12.03 hor クリア を 切替え に変更 void CViewCommander::Command_SEARCH_CLEARMARK( void ) { this->m_pCommanderView->m_bCurSrchKeyMark = ! this->m_pCommanderView->m_bCurSrchKeyMark; this->m_pCommanderView->RedrawAll(); return; }
これで本当に Ctrl+F3が検索語ハイライトの切り替えになったし、パターン検索でも問題がなくなった。2001年から現在までに何かがあったのだろう。
<追記@2010-06-20> 検索条件が選択文字列で置きかわるのは意図された動作だった。つまり「検索マークの切替え(Command_SEARCH_CLEARMARK)」は選択文字列をハイライトしたいときに実行するコマンドだということ。ハイライトが消えるのは選択文字列が空のときに限られた動作。はっきりいってこれは名前が悪い。「クリア を 切替え に変更」したときに主客転倒してしまってる。別のコマンドにせーよ。 </追記>
脈絡もなく突然死する。abnormal program terminationと出るから、throwしてる部分が原因。どうしてそこを実行するような状態になるのかがわからない。色分けの反映が、外因による再描画が起こるまで遅れることも、やはり前後の脈絡なく起こる。徐々に壊れていってるのか?
昨日の突然死はコードと仕様を整理して適当に書き換えてるうちに出なくなったみたい。だがその過程で新たなミスを仕込んでいた。無効化されたイテレータ。変数名にしてたった 5文字のミス。そのミスをした次の行ではちゃんと意識して避けていたのに……。これのせいで描画範囲の最適化(いまはわかりやすさから常に全面を書き換えている)が進まなかった。
仮想関数を持ったベースクラスがあって、継承するからには必ずオーバーライドしないと意味のないメンバ関数を = 0; で純粋仮想にして、そうでない、必ずしもすべての派生クラスにとって必要ではないものをデフォルト付きの仮想関数にしていた。具体的にいうと、デフォルト付きの仮想関数は正規表現キーワードのためだけに用意されたようなもののことだ。
気付かなかった。継承した正規表現キーワードのクラスでオーバーライドに失敗していたことに。関数が constか非constかの違いで失敗していて、別の仮想関数を定義していただけだった。だから C#には overrideキーワードがあるんだよぉ。 .NETが 1.0βのときから*7落とし穴だって事は知ってたさ。気付くまで、ちょっと、丸一日かかっただけで……orz
派生クラスで分かり易さのためにわざわざ virtualって書いてるんだから、そのときに overrideって書かせてくれていたら、コンパイラが「おまえ何をオーバーライドしようとしてるんだ(そんな対象は存在しない)」って教えてくれていたはずだ。
関連。C#のえろい人の話>「Versioning, Virtual, and Override」
degradeの回復はもううんざりだお。次はこれをする。
パターン内での色分けに対応した。
JavaScriptの正規表現と違ってキャプチャの位置がわかるので、SHJSの仕様に従ってかっこを
/(A)(B)(C)/
というように隣接させる必要がない。入れ子にすることも許される。入れ子にしたら一番内側の色が適用されるようにしたつもり。
@2009-10-11に書いた Ctrl+F3での検索語ハイライトの切り替えについて。ANSI版、UNICODE版に共通するパターン検索の問題として、ハイライト対象かどうかの判断が(おそらく)常に行頭からのマッチングによっているの対して、F3や検索ダイアログの検索開始位置はキャレットのある場所からと、異なっている。\w{10} というパターンを検索するとき、ハイライトは行頭から探して 10文字を対象とするが、検索はキャレットの位置から探した 10文字を選択する。
検索語ハイライトのやり方を変えないといけない。
先に、さらに別の問題の、上検索で行末からの検索が行われていなかったのを修正した。たとえば、"0123456789" という数字10文字の行があって、行末から \d{6} を上検索したときに "012345" が選択されてしまい、"456789" ではない、という問題。
まだ直っていないのはキャレットを "3" と "4" の間に置いて \d{6} を下検索したときに、ハイライトの範囲が(行頭から探した数字6文字) "012345" なのに対して、選択範囲が(キャレットの後ろから探した数字6文字) "456789" となり、一致しない問題。
検索語ハイライトのやり方を変えた。パターン検索でも検索開始位置を尊重したハイライトができるようになった。ただし、従来の色分け方法では、CColor_Found::BeginColor()や CEditView::IsSearchString()に与えられる引数が十分でないために、検索開始位置に配慮した判断ができない。というわけで、ここまでの一連の変更に対する上積みの修正となった。(本家へのパッチは UNICODE/ANSI版ともに作成できません)
@2011-03-31 ハイライトが原因で1行3000桁を超える程度のファイルで検索がすんごく遅くなってたので、ハイライトは検索開始位置を考慮しないように戻した。
Luaちゃんのためにブロックコメントを行コメントより優先した。Luaのブロックコメントは --[[ から ]] までで、行コメントが -- から行末まで。rkw2/Lua.rkw2ファイルを用意して正規表現キーワードで色分けすればなんなりと対応できるけど、コメントに関しては専用フォームが用意されてるからそちらでできた方が良い。ひょっとして Luaとは逆のパターン:行コメントを優先しなければいけない言語があるだろうか? その場合、開始文字の長さでソートして長い順に色分け機能を登録しないといけなくなるが。(ところで Luaにはデクリメント演算子がなさそう。コメントより強調キーワード、強調キーワードより正規表現キーワードの方が優先順位が高いので、どちらかで -- を登録するとコメントじゃなくなってしまうだろう。こっそり書いたが、強調キーワードに記号が登録できるように仕様変更したつもりだ。英字とハイフンを混ぜられるわけではないが、&& みたいに全体が記号だけならいけるはず)
>>'\0'も置換できるように >正規表現で \x00 とすれば出来ます。
無理。俺は NUL文字を考慮していない。
> // 前の行のNULL文字(\0)にもマッチさせるために+1 2003.05.16 かろと
というコメントを CSearchAgent::SearchWord() @ CSearchAgent.cpp で見つけたときに嫌な予感はしたけどもう遅い。
設定ファイルのことを考えるときには共有メモリのことを考えないといけないんだ*8。個々のプロセス(=一つの文書)にとって必要なタイプ別設定は一つだけだから共通設定と基本設定だけを共有メモリに置いておいて、他のタイプ別設定は個別に自分のメモリ空間にロードすればいい気がするけど、そうするとタイプ別設定の同期が必要。変更して iniファイルに保存したときにブロードキャストするだけ? タイプ別設定を共有メモリに載せる必要はある? > タイプ別設定数を可変長に
正規表現インクリメンタルサーチなんて機能がある! ひえぇ。
>正規表現による複数行検索対応(簡易版) 正規表現ライブラリに適切なインターフェイスがない限り、ファイルサイズに比例したメモリを消費しないと完全な対応はできない気がする。
既存の APIに適合させるための非効率的なごり押し(に思える)や、バッファに格納する行数を事前に入力させるような設計(行数に制限があることと設定の手間。あと、指定させるなら行数よりバッファサイズでしょう。ユーザーの利便性よりコンピュータの効率よりプログラマの都合を優先させてどうするの?)には尻込みしてしまう。たとえば <table>タグで囲まれた部分を探そうとしたときに、その中身が最大で何行ぐらいあるだろうかとか、考えたくはない。
色分けのために行ごとに保存するキャッシュを、シフトする機能は価値があるかも。数MB、35000行程度のファイルで、一行削除する度にスクロールを待たされるのはたまらない。マシンスペックにもよるが、PageDownキーを押し続けるだけでは待たされない。Ctrl+Endやスクロールバーを掴んだときに顕著。しかもこのファイルは色分け対象ではなかった。さらに遅くなるってことだ。
昨日の空の選択範囲が消えない対策として空のときは範囲選択をしないことにしたら、今度は下検索が先へ進まない。現在の選択範囲が空かどうかでその場足踏み対策をしていたからだった。やっぱり正攻法の、空の選択範囲の表示をキャレットの移動に伴ってきちんとクリアすること、で解決するのがよさそう。そのへんのコードの流れがよくわからないから手を付けなかったんだけど。
空の選択範囲はキャレットと表示がかぶってしまって、キャレットの点滅が一巡するまでキャレットを見失うデメリットもある。いっそ描画しないのがいいのでは。それがいいとは思わないけど、幅のない矩形選択範囲も現在は描画されていないのだし。
わかってきた。空の選択範囲が消えないのは改行記号を描画していないからだ。
というわけでもなかった。
選択開始時というのは幅0の選択をしている状態である。そのときにCViewSelect::DrawTextArea()を呼ぶことで選択範囲の始点の反転・反転解除の対応がとれたと思う。空の選択範囲のゴミは残らなくなった。そのおかげで幅0の矩形選択範囲を不具合なくある程度の幅で描画できるようになった。
そもそもどの変更でゴミが残るようにしてしまったんだろう?検索と色分けのあたりしかさわってないが。
// 2005/04/02 かろと 0文字マッチだと反転幅が0となり反転されないので、1/3文字幅だけ反転させる // 2005/06/26 zenryaku 選択解除でキャレットの残骸が残る問題を修正 // 2005/09/29 ryoji スクロール時にキャレットのようなゴミが表示される問題を修正
こういうコメントが残っていて、繊細な部分だったという言い訳はできそうだ。
ここ数日は一退一進で進んでいない。
18日から 20日にかけて、exeのサイズが 50KB、diffのサイズが数KB縮んでいる。mergeのやり方を変えたんだが、失敗しているとしか思えない。
色分けは別スレッドにするのがいいな。CPU使用率を(3コアだから) 33%にはりつかせて、ユーザーをなすすべもなく待たせて、やってることが色分けだなんてアホすぎる。>@2009-10-19
中心となるデータ構造は一つ。行ごとの色分け開始状態。コンテキストといってもいい。前の行から続くコメントの中なのか、文字列の中なのか、一番外側なのかを表した配列。
色分けスレッドは、未確定の要素のうち一番小さいインデックスを持つもの、の一つ前の行をひたすら色分けして確定に変えていく。
メインスレッドは、行の内容に変更があったからその次の行が未確定に変わったこと。ある行からある行にかけて削除されてしまったからその後ろが軒並み未確定に変わってしまった(賢ければ、対応する要素を取り除いて後ろの要素を前にシフトし、未確定にするのは先頭の一要素だけで済ませてよい)ことを知らせる。色分け結果はタイムアウト付きで受け取り、間に合わなければ色分けなんてせずに文字の表示を優先する。
こういう機能はどうやったら手に入るだろう。特に 2番目と 3番目。イベントとかシグナル? C++標準は無し?
そもそも、配列を所有するオブジェクトがこういう機能を提供するのだろうけど、そのオブジェクトはどのスレッドに属しているのか。第3?
違う違う。両方のスレッドがそのオブジェクトのメンバ関数を起動するから排他処理が必要になるんだって。
Unicodeリテラル(>20091007)といい、正常進化だよね。>「C++0xのマルチスレッド機能(2/3):CodeZine」難しい部分はコンパイラとライブラリ作者に任せて、便利な部分を享受するだけの末端アマグラマ拝。
非キーワード文字を含む強調キーワードの色分けを可能にした。空白が入っていてもハイフンが入っていてもバッククオートで始まっていても、キーワードの先頭と末尾が語の境界にありさえすれば色分けする。今は文字を 3種類に分類して語の境界を検出している。即ち、空白類、キーワードに使用できる文字、キーワードに使用できない文字。おおざっぱだけど結構大丈夫。この変更は単独パッチにできる。
正規表現でたとえると、従来の色分けは \b\w+\b を色分けする。昨日までは \b(\w+|[^\w\s]+)\b を色分けしていた。今日のは \b\S.*?\b を色分けする。(\bは \wと [^\w\s]、\wと \s、[^\w\s]と \sの境界)
弱点は、最長一致ではないから共通の接頭辞を持つ複数のキーワードが一緒に登録されていると長い方は絶対に色分けされないこと。A-Bと Aが登録されていたとき、A-Bは絶対に色分け対象にならない。面倒だけどキーワードセットを二つに分けて、長い方を強調キーワード1、短い方を強調キーワード2に割り当てると、番号の若い強調キーワードが優先されるので、長いキーワードも色分けできるようにはなる。
<追記@2010-04-04> やはりというか、掲示板(unicode:1156)で最長一致について言及されてしまった。rev2パッチで最長一致に対応したつもり。 </追記>
昨日おかした間違いにトイレで気がついたよ。<どうでもいい
長さが足りなくて登録キーワードと一致しなかったときに、キーワード文字かどうかに関わらず続く語を加えて、改めてキーワードかどうかお伺いをたてるようにしたのが昨日の変更。それで、その結果が結局不一致となったときにカーソルを巻き戻すのを忘れていたせいで、キーワードの見落としが起こっていた(はずだ)。
予想通りだった。修正した。
こういうケース。
# 強調キーワード(2つ) 本日は、晴天なり 晴天じゃないよ # テキスト(1行) 本日は、晴天じゃないよ。
晴天じゃないよ、の部分が見落とされていた。
ハイライトの描画が古い情報に基づいていたのを修正した。なにが大丈夫だから省略、だ。> 過去の自分
@2009-10-20での矩形選択始点にゴミが残る問題の修正の結果、普通の選択でゴミが残るようになっていた。それならこれでどうだ。
組み込みの「半角数値」の色分けが単語の境界を認識せず、数字とみれば見境なく色分けするようにしてしまっていたのを修正した。
検索語ハイライトがおかしい。CEditView::IsSearchString()の仕様が正規表現検索とそうでない場合で異なっていたことに気付かず、正規表現を使用しないときは、一行につき一つしかハイライトできていなかった。正規表現を使用した検索でも、ハイライトすべき語が周期的にスキップされてしまう。
修正した。
@2009-10-10で書いた、「どなたかの受け売りで代入( = など)と演算子( == など)を分けた。この日記での色分けも以前からそうしている。」の元ネタを再発見した。これを以前読んでいたのだ。
With syntax highlighting it would be possible to mark "=" and "==" in different colours. Yay! A good reason for implementing syntax highlighting! But — and at this point it probably won't surprise you — every colour scheme I've come across uses the same colour to highlight both "=" and "==".
ちょっと考え直して正規表現キーワードの優先順位を、強調キーワードの下に下げた。正規表現キーワードのフォーマットは難しく、強調キーワードのは簡単なので。「何でこのキーワード、登録して設定したのに色分けされへんの?ムキー!」ってならないように。(容易に想像できる通り、今日の自分がそうだったからです)
正規表現キーワードの色設定のチェック状態が反映されていなかったのを修正した。以前の正規表現キーワードと違い、一つのパターンが一つの色設定にひもつけられるわけではないので、チェック状態は機能の ON/OFFを意味しない。チェックが外れていた場合は「テキスト」色に色分けすることにした。
>複数検索結果のハイライト表示 Request/197 - SakuraEditorWiki
最初に一言。検索履歴を利用して複数の色分け対象を探すのは、使いやすいインターフェイスがみつからないだろう。
検索機能は大枠で三種類ある。パターン検索、単語検索、それとただの検索。今回変更したのは単語検索。単語検索では検索語が一単語でない場合、たとえば空白やピリオドやハイフンを含んでいた場合、これまでは絶対にマッチが見つからなかった(と思う)。だから検索語を単語に区切って各々を違う色に塗り分けても悪影響はないだろう、むしろ望むところ?という判断。
ツールバーの検索ボックスを単語検索専用にするか別に用意すれば、すごく使いやすくなる。
検索文字列の色設定のチェック状態を反映するように修正。
Very Sleepyを試してみた。Ctrl+Endを押すと wcschr()がものすごい勢いで呼ばれているらしい。それを呼ぶのは文字変換系などを除くと WCODE::IsZenkakuKigou()がくさい。さらにそれを呼ぶのは CWordParse::WhatKindOfChar()。これは単語の境界判定で使われている。
改行文字36223個、折り返し64935行のファイルの先頭で Ctrl+Endを押すと、以下のような呼び出し履歴を伴って CWordParse::WhatKindOfChar()が○百万回呼ばれていた。笑うしかない。と書こうとしたが終わらない。回数は数えられなかった。どうやら Visual Studioが張り付いた状態では限界が低くなるらしい。このファイルの内容を、サイズが 100MBを超えるくらいまでコピペを繰り返したファイルでも無限ループ状態になるのを確認している。(何時間も終わらなければ無限でしょう)
sakuraW.exe!CWordParse::WhatKindOfChar(const wchar_t * pData=0x027dd078, int pDataLen=134) 行 120 C++ sakuraW.exe!CWordParse::WhereCurrentWord_2(const wchar_t * pLine=0x027dd078, int nLineLen=134, int nIdx=125, int * pnIdxFrom=0x0017f76c, int * pnIdxTo=0x0017f768, CNativeW * pcmcmWord=0x00000000, CNativeW * pcmcmWordLeft=0x00000000) 行 49 + 0x10 バイト C++ sakuraW.exe!CEditView::IsSearchString(const CStringRef & cStr={...}, int nPos=125, int * pnSearchStart=0x026338f4, int * pnSearchEnd=0x026338f8) 行 307 + 0x24 バイト C++ sakuraW.exe!CColorML_Found::IsStartOfKeyword(const CEditDoc * const pDoc=0x02740048, const int nLineNumber=40057076, const int nPosWithinLine=125, EColorIndexType * const outColor=0x0017f80c, void * * userData=0x04af1e94) 行 56 C++ sakuraW.exe!ColorMLStrategy::HighlightEngine::DoHighlightLine(const std::vector<CColorML_Base * const,std::allocator<CColorML_Base * const> > & strategies=[2](0x026338a8,0x026338e8 {pView=0x0263a458 lineNum=1582 begin=-1 ...}), const CEditDoc * const pDoc=0x02740048, const int nLineNumber=1582, const ColorMLStrategy::HighlightEngine::StartingStrategy & startingStrategy={...}, ColorMLStrategy::Result * const outResult=0x00000000) 行 113 + 0x27 バイト C++ sakuraW.exe!ColorMLStrategy::HighlightEngine::HighlightLine(const std::vector<CColorML_Base * const,std::allocator<CColorML_Base * const> > & strategies=[2](0x026338a8,0x026338e8 {pView=0x0263a458 lineNum=1582 begin=-1 ...}), const CEditDoc * const pDoc=0x02740048, const int nLineNumber=36202, ColorMLStrategy::Result * const outResult=0x0017f9bc) 行 69 C++ sakuraW.exe!ColorMLStrategy::HighlightLine(const CEditDoc * const pDoc=0x02740048, const int nLineNumber=36202, ColorMLStrategy::Result * const outResult=0x0017f9bc) 行 200 C++ sakuraW.exe!CEditView::DrawLogicLine(HDC__ * _hdc=0x00008d6a, DispPos * _pDispPos=0x00000000, int * pnLineTo=0x0017fbe4) 行 546 C++ sakuraW.exe!CEditView::OnPaint(HDC__ * _hdc=0x01011734, tagPAINTSTRUCT * pPs=0x0017fc38, int bDrawFromComptibleBmp=18) 行 377 C++ sakuraW.exe!CEditView::DispatchEvent(HWND__ * hwnd=0x00080684, unsigned int uMsg=0, unsigned int wParam=0, long lParam=0) 行 693 C++ sakuraW.exe!EditViewWndProc(HWND__ * hwnd=0x00080684, unsigned int uMsg=15, unsigned int wParam=0, long lParam=0) 行 103 C++
CLayoutMgrの実装もリスト。ランダムアクセスの遅さ(=CLayoutMgr.SearchLineByLayoutY()の遅さ)がスクロールバーをドラッグしたときのカクカクした反応に現れている。メモリは潤沢にあるものとして、CPU速度と相談していくつまでならリストをたどっても満足なレスポンスを返せるかを決める。たとえばそれが 10000なら 10000行ごとにショートカット用のポインタを持っておけばいい。
インデックスのはりすぎは更新のコストがいやんなことになる@2013-07-26。10000行ごと、って決めてしまわずにルーズに管理して更新頻度を減らそうとすると、直近のショートカットを見つけるのが割り算一発からバイナリサーチになるなあ。
ポインタ二個分のオーバーヘッドをもった doubly-linked listなんかより、つなぎ合わせた vectorでいいじゃない。(そうするとこんどはリターンキーのレスポンスが気になるわけだけど)
Alphaは、GreenPadとならんで、ソースを参考にしたいエディタのにおいがする(まだチラ見もしてないけど)。Alphaのリポジトリの最終更新は 43時間前と、まだ生きてるのも嬉しい。
GreenPadはねえ、どこに機能があったのか?と見返してしまうほどにひとつひとつのファイルはすかすかで整然としてる。だからといってべらぼうにファイル数が多いわけでもない。テキストエディタなんてその程度の規模のアプリなんだと思わせてくれるのだけど、自分で書いたら管理不能なスパゲッティになるのが見えていて、知識とテクニックと設計能力の差を思い知るよね。
Alpha-0.7.5.16α-fix10で .rbファイルを開く。#のみの行で #がコメント色にならないとか、なんで設定変更できないの?とか枝葉末節はおいておいて、機能性もレスポンスの良さも申し分ない。文字の表示がきれい。軽やか。紙切れのようだ。100MB超のファイルを開くと CPU使用率 33%(1コアフルロード)でフリーズして応答が戻らないけど、そんなん耐久テストのみの世界だし。正規表現で複数行検索できるのが何よりすばらしい。それはやはり、Boost.Regexだからできたのかなあ >「2006-12-22 Boost.Regex で改行をまたぐ検索を その 8 (Alpha の अनुपयोगी な日記)」。入力はイテレータかストリームで与えたいよねえ。おっと、\w{10} の前検索が戻っていかない。(毛色の違う先端バージョン 0.7.9x系列の)Alpha-0.7.93.5αや、\w{10}\n というパターンだとバックしていくのだが。Alpha-0.7.93.5αは選択範囲の D&Dで落ちた。本当にアルファ版なんだ。
ソースの前に、日記(Alpha の अनुपयोगी な日記)も読み応えがあるなあ。グリフって何?レベルの人間には Unicode関連の話が理解できない。内部文字集合が Unicodeってだけでなく、すごく真面目に Localization(でいいのかな?)に取り組んでるのが伝わってくる。それとメモ帳。エディタを名乗るからにはこけおどしの機能をてんこ盛りにする前に文字の表示で、(世界中で販売されている Windowsに添付されている)メモ帳品質を超えていてほしいものだ。
昨日は「Localization(でいいのかな?)」と書いたけど、2006年あたりの日記(Alpha の باطل な日記)を見てると Multilingualizationまで踏み込んでいる気がする。「気がする」のは用語の適用範囲がいまいちわかっていないから。Unicodeがモノリンガルであるということの意味がそもそもわからん。
破ってしまいました。
@2009-10-17に書いた「上検索で行末からの検索が行われていなかったのを修正した。たとえば、"0123456789" という数字10文字の行があって、行末から \d{6} を上検索したときに "012345" が選択されてしまい、"456789" ではない、という問題。」に伴う副作用だと思う。気付いたきっかけはこの日記。
サクラは '00000'00000 ←→ 00000'00000' だから、挙動に納得は出来る。 EmEditorは一見、真魚と同じに見えるが、 aaa1aaa1aaa1に対して、.+1で検索すれば、 順方向では開始位置から最後までがヒットするのに逆方向ではa1だけがヒットする。 これも全く納得できない理解不能な動作だ。
上検索で a1 ではなく aaa1aaa1aaa1 にマッチするようにはできる。副作用だけを取り除けるはずだ。正規表現マッチの基本は greedyだから、上検索で a1 にしかマッチしないのは自分も納得できない。
真魚の最新版(2.2.3.5)で aaa1aaa1aaa1 に対して .+1 を下検索すると全体がマッチするのに、上検索すると a1 に三回マッチするっていうのは全く納得できない理解不能な動作だ。どこかで心変わりしたのですか。
気にせず greedyな上検索にしてみたけど、中途半端な結果になった。
本家のサクラエディタは 3番目に対応している。上検索の実装が(行頭から始まる)下検索の巻き戻しだからだ。ところがそれではキャレット位置からの上検索ができないからと変更したのだった。その結果、下検索を繰り返してキャレットが進んでいくたびにキャレットより前のハイライト範囲(=上検索でのマッチ範囲)がうぞうぞと変化するようになったと自分で報告していたのだから、最初から上検索と下検索の対称性などのぞむべくもなかったのだ。
対称性は(自分の望んだ結果なのだから)捨てられるとしても、2番目と 3番目にはよりよいマッチが存在しているのに対応することができない。ひょっとしたら 2番目は鬼車の APIを直接使って「検索対象文字列の終端アドレス」とは違う「検索対象文字列の検索終了位置アドレス」を渡すことで、$が誤ってマッチする心配なしに、aaa1aaa1にマッチさせられるかもしれない。でも 3番目は .NET Frameworkの「RegexOptions 列挙体 (System.Text.RegularExpressions)」に用意されている RightToLeftオプションのようなものが予め用意されていなければ自作するしかない、ような気がしたが、\1 のような参照とキャプチャの前後を入れ替えられるわけもなく、.+?1 の上検索が a1にマッチするのを期待するのは無茶ってものだ。
行末やキャレット位置からの(中途半端におわってしまう)上検索より、(行頭からの)下検索の巻き戻しのように(大部分で)対称的に動作する上検索が、わかりやすさも使い勝手も勝ってるようだ。(すでに加えた変更を巻き戻すかどうか迷う)
同じく、真魚の作者の日記から
正しくは、 CR:← LF:↓ CRLF:←曲がって↓ LFCR:↓で一行、←で一行 こんな表記になる。 あきらかに間違っているのはサクラエディタで、CRとLFの矢印が逆だ。 いや、Windowsの間違いにわざと乗ってやってると言うべきなのか。 CR:↓(逆) LF:←(逆) CRLF:↓曲がって←(逆+逆) LFCR:←曲がって↓(一行にまとめてはいけない)
今は ANSI版、Unicode版ともに CRと LFの逆転は解消され、CRは←で、LFは↓で描画されている。CRLFはそのままだがこれは、LF(↓)と CR(←)の組み合わせなのではなく、リターン記号(cf.リターンキー(ja.wikipedia.org))だったんだよ、Windowsにおいて改行と CRLFとリターンキーは切っても切れない関係なんだから、という強弁で乗り切ろう。
URLの色分けがいつからかできてないや。
2009-11-16からだ。アルファベットかをどうか調べる関数に、文字(wchar_t)のかわりにその文字の文字列中でのインデックス(int)を渡していた。
intと wchar_t(オプションにより組み込み型扱いになっている)は全くの別物だと思うんだけどなあ。
相変わらずマージってやつは泥臭い作業で自信が持てない。同じコードブロックが複数回連続してるような場所がみつかってもまったく驚かないよ。
行をまたぐ色分けに問題があって、たとえば一画面に収まらない長大な複数行文字列があったとして、何度も上下にスクロールしたり改行を入力したりするだけで色分けが変わってしまっていた。
括弧類を色分け対象にしていて、「対括弧の強調」も有効になってる場合、キャレットが括弧から離れるときに、閉じ括弧の表示が閉じ括弧の次の文字と同じになってしまう。
このブランチ単体でも、純粋な公式 trunk2でも再現しないからその差分を見ていたら見つけた。
http://sakura-editor.svn.sourceforge.net/viewvc/sakura-editor?view=rev&revision=1723
Index: sakura_core/view/CEditView_Paint_Bracket.cpp =================================================================== --- sakura_core/view/CEditView_Paint_Bracket.cpp (.../shjs_style_regex_keyword) (リビジョン 45197) +++ sakura_core/view/CEditView_Paint_Bracket.cpp (.../build_my_sakura) (リビジョン 45197) @@ -138,7 +138,7 @@ if( IsBracket( pLine, OutputX, CLogicInt(1) ) ){ // 03/10/24 ai 折り返し行のColorIndexが正しく取得できない問題に対応 // 2009.02.07 ryoji GetColorIndex に渡すインデックスの仕様変更(元はこっちの仕様だった模様) - nColorIndex = GetColorIndex( pcLayout, OutputX ); + nColorIndex = GetColorIndex( pcLayout, OutputX + 1 ); } else{ SetBracketPairPos( false );
+1したら次の文字の色になるのも当然。GetColorIndex()の中身がブランチの方で別物になってるから、公式で行われた上のような呼び出し部分の変更はマージしてはいけなかった。
ここらで続く。
sakuraW+rkw2.zip (645KiB, 2011-05-25, 2.0.2.0(r1913)ベース)
shjs_style_regex_keyword(trunk2@1711).patch (352KiB, 2010-04-14)
svn co https://sakura-editor.svn.sourceforge.net/svnroot/sakura-editor/sakura/trunk2/@1711
気ままな変更がちらほら。
色分けされないときのチェックリスト
Javaだった java.util.regex.Matcherクラスの requireEnd()や hitEnd()がそう。他にも AnchoringBoundsや TransparentBoundsが用意されているなど至れり尽くせり。こういう生真面目さが Javaの良さであり、冗長さや遅さを許す一因だったのかも、と今は思う。
* WikiPedia(ja)を見たら JSONで表現できるデータタイプに正規表現がない!ポータブルじゃないからか? ほかにも有効でないエスケープシークエンス(\Gとか \qが Gや q自身を表す)とか \(改行)というエスケープシークエンスが無効。シングルクォーテーション文字列もない。JSON_checkerによるとマイナスの後に空白を入れるのも、プラスを明示的に付けるのも許されない。窮屈だけど、\x[ や \x] というエスケープシークエンスの存在が正規表現パターンの簡易的な解釈を一段面倒なものにしていると感じていた(>[[20090922p01]])ところなので、悪くはない。
⁑ 内部で使用するだけの型の完全な情報を実装(cppファイル)に閉じ込めようと思ったら、ヘッダの中のクラスはその見せる必要のない型をポインタで保持するしかないのか? privateメンバーの詳細(サイズとか)なんてどうでもいいから隠したいのだが、ポインタで保持することにするとコンストラクタで newすることになるのが嬉しくない、というジレンマ。デストラクタが呼べないからと std::auto_ptrにすることもできず生ポインタをメンバにしないといけないのも困りもの(明らかに「所有」しているのに、生ポインタではそれが伝わらない)。
⁂ @2009-10-24 デストラクタが呼べないのは、auto_ptrをメンバに持つクラスのデストラクタがコンパイラの生成するデフォルトのデストラクタだったから。ヘッダでデストラクタを宣言して実装で空のデストラクタを書けば解決。ビョルン・カールソン『Boost』(2008, ピアソン・エデュケーション)に書いてあった。「implクラス(構造体)の寿命管理を scoped_ptrに任せ、デストラクタから pimpl_(implオブジェクト)の delete文を削除する(scoped_ptrを使えば deleteは不要となるのです)だけで作業は完了です。ただ、デストラクタそのものは定義しておく必要があるということを覚えておいてください。その理由は、コンパイラが暗黙のデストラクタを生成する時点では型 implが不完全であり、pimpl_のデストラクタを呼び出せないためです。implの格納に auto_ptrを用いた場合、こういったエラーを含むコードがコンパイルされてしまうことになりますが、scoped_ptrを用いることでエラーを検出できるのです。」auto_ptrの場合に起こる「こういったエラー」が何なのかわかっていないが……。
*4 @2010-01-29 『More Exceptional C++』を読み返していたら、これにも原因の説明と対処法が載っていた。読んでいたはずだけど、一度自分で穴にはまらないと気付きにくい問題だろうね。
*5 今のままでは正規表現のフラグとフラグの間にコメントが埋め込めそう。コメントは空白文字だとして、returnStateに戻る代わりに state_transition_table[returnState][C_SPACE]に戻れば良さげ。
*6 現在までに書いた C++コードの 99%以上がここ 1、2か月のものだという、にわかではあるけども。
*7 というか、そのときの C#しか知らない。
*8 共有メモリにオブジェクトを置いて初期化されていないゴミメモリにアクセスしたり、だったら placement newの出番だ、とコンストラクタを呼んだんだけど、(想像では)似非共有オブジェクトが私的に確保したメモリは共有されてなくてやっぱりエラーになった、少し前の記憶がよみがえる。
@2013-07-26 Scintillaでの行管理の工夫。「[[Scintillaのデータ設計 - maneman8000の日記|http://d.hatena.ne.jp/maneman8000/20110206/1297006996]]」インデックスの更新が必要なエリアはある点から始まり必ず末尾で終わる。ある点をひとつ記憶しておくことで更新範囲をある点とある点の差分にすることができる。
*9 文字列の内部表現が Unicodeなのに \xXX という ASCIIコード指定を、意味が変わらないように解釈してくれる。
速くなると聞いては捨ておけぬ。
category_anchorでの nil.yearエラーは、起こったのがカテゴリモードだったら既知だけど最新N日表示だから違うし、TDiaryBase@dateは読み出し専用プロパティだから(navi_user.rbのような荒技を使ったりしない)プラグインには変更できないし……。(結局わかりません)
plugin/makerss.rbが直前に変更のあったセクションの他に、過去にちょっとした修正のあったセクションを *.rdfのエントリに加えてしまうのを防げるので賛成。一度考えた回避策はちょっとした修正が *.rdfに反映されてしまうので影響が大きくて断念したし。
という手順を考えた。
セクションインデックスがずれるような変更だった場合は影響範囲が無駄に大きくなるけど、セクションインデックスはセクションIDを兼ねているのでやむなし(URLだって変わっちゃってますから)。
新規作成や修正されたセクションに適用される最終更新日時を、一回の「編集」や「追記」につき一つに限らないと、update_procの中で、変更のあったセクションを見つけるときにアバウトな処理をするはめになりそう。(心配しなくてもそうはならんでしょうが)
ネタバレ注意!
member()があっさり 0 を返しすぎるのはすぐわかった。コメント欄の、ハッシュ表があふれることを否定しない表現「『要素の個数はハッシュ表のサイズBより小さいとする』という前提なので、『全部埋まっていたら』の意味にもよりますが」を読んでいて、前提条件に関わらず、同じ要素が複数回 insert()されるとハッシュ表があふれるのもわかった。さて 3つめは? そして修正版は?
(member()相当の機能が欲しいだけなら)ソート済みの int配列にすれば…… < ハッシュテーブルじゃない!
意外にできる子とはいえ insert()と delete()でのメモリブロックの移動コストが気になる。一定サイズの配列(Chunk)をリストで管理したら……とかどうでもいい。
(元記事コメントから)> 削除済みマークを付ける
delete()で EMPTY(0)にせず DELETED(-1とか)にするのね。clear()するまで不良資産(DELETEDな要素)は貯まる一方、というわけでもなく、insert()するときに再利用できそうだ。
これ(削除済みマークを付ける)って FAじゃないのかなあ。
とりあえず、気付いた 2つの問題(delete()がからむと member()が不正確。重複要素であふれる)に対処した insert()はこう。EMPTYな要素より DELETEDな要素を優先して使用したくてフラグっぽい変数が増えてしまった。
/* 要素xをハッシュ表hに追加 */ void insert(int x) { int i = hash(x); int j = -1; /* i より優先して xを代入すべき位置 */ while (h[i] != EMPTY) { if (h[i] == x) { /* return; */ j = i; break; } else if (h[i] == DELETED) { j = i; } i = (i + 1) % B; } h[j != -1 ? j : i] = x; }
delete()の方も、EMPTYの代わりに DELETEDを代入するように変更する。
重複要素でハッシュ表があふれることがなくなったとはいっても、insert()と delete()を繰り返すうちに DELETEDな要素が増えていって EMPTY要素の数が 0になる可能性はないだろうか。EMPTY要素がなくなると、存在しない要素xを引数にした member()が無限ループに陥る。直観的にそういう可能性はない気がするけど……。
完全ハッシュ関数の状態からスタートしよう。ハッシュテーブルには最低でも一か所の穴(どの要素xも配置されない)がある。この位置を一時的にでもある要素で埋められればハッシュテーブルの全ての要素を DELETEDにできる。直接この穴に配置される要素xは存在しないからハッシュ値を衝突させて insert()で玉突き処理を行わせなければいけない。完全ハッシュ関数を二つの要素のハッシュ値だけが衝突するように変更してみよう。玉突きで穴を一つ塞ぐことができるが、完全ハッシュ関数ではなくなることで穴は二つ(以上)になっていた。以下略。
拍子抜け。要素を重複して格納せず、DELETEDな要素を再利用している限り、ハッシュ表が DELETEDであふれることはない。あっけなさすぎて自作自演臭がしてきた。
蛇足も蛇足の別バージョン。全然かわりばえしない。
/* 要素xをハッシュ表hに追加 */ void insert(int x) { int i = hash(x); int d = -1; /* hash(x)に一番近い DELETEDの位置 */ while (h[i] != EMPTY && h[i] != x) { if (d == -1 && h[i] == DELETED) { d = i; h[d] = x; } i = (i + 1) % B; } if (d == -1) { h[i] = x; } else if (h[i] != EMPTY) { h[i] = DELETED; } }
0 ≦ hash(x) < B は暗黙の前提にしてるけど、3つ目のミスだったりして。
コメントも読めばわかるけど、ミスが 1。修正過程で陥りやすい落とし穴が 2つくらい、なんだってば。(でもわかりにくい)
最終更新: 2010-01-15T21:38+0900
C/C++ではもちろん0==false はtrueなわけですが・・・ もしかして、boolean型を整数型とキャスト比較しない仕様なんでしょうか?
この辺はC/C++も難しくて 本質的には 2==trueも本当は真なんですが偽になったりもします。
Rubyでビットフラグをチェックするときは 0との比較を忘れてはいけない。
if(flag & mask) # (意図に反して)必ず実行される end if(0 != flag & mask) # 期待通り end
というのはさておき、2==true に関して、「本質的には」という言葉が胡散臭い。その「本質」を表すコードは「(2!=false) == (true!=false)」であって、2==true は関係ないのでは? ==は真偽値を返す演算子ではあっても、真偽値のみをオペランドにとる演算子ではないから、2==true も 1==true も -1==true までもが暗黙にそのように解釈されて真になったりしたら、むしろ気持ち悪い。それは PHPの世界だ。(偏見)
Cと逆だな
Rubyでは非0が偽だと言いたげですね。
Safe Bool (ja.wikibooks.org)という C++のイディオムがあって、つまり裏を返すと、危険な Boolean変換が C++には存在するということだ。やーめーてー。
C++09(予定)では explicitなコンストラクタみたいに、明示的なキャストを必要とする変換を定義できるようになるらしい。
先人 > Amazon Product Advertising APIの認証の件 - zorioの日記
Ruby-1.8.7と Ruby-1.8.6では String#force_encoding("ASCII-8BIT")ができず、String#ordもない(ないのはエンコーディングの概念がないからと、String#[]で代替できるからだと思われる)。それらを使い分けるために 2種類のメソッドを用意するくらいなら、unpackで配列経由でいいです。
require 'digest/sha2' def hmac_sha256(key, message) hash = Digest::SHA256 hash_block_size = 64 # bytes (= hash.new.block_length) key = hash.digest( key ) if hash_block_size < (key.bytesize rescue key.size) ikey = Array.new( hash_block_size, 0x36 ) okey = Array.new( hash_block_size, 0x5c ) key.unpack("C*").each_with_index{|key_byte, i| ikey[i] ^= key_byte okey[i] ^= key_byte } inner_hash = hash.new.update( ikey.pack("C*") ) outer_hash = hash.new.update( okey.pack("C*") ) digest = outer_hash.update( inner_hash.update( message ).digest ).digest return digest end
短い秘密鍵は 0を補うって書いてあった。その処理が見あたらないのになぜうまくいくのかと考えたら、0を相手に排他的論理和をとったって何も変わらないのねん。
class Digest::Base
- update(str)
- self << str
- 文字列を追加する。self を返す。複数回updateを呼ぶことは文字列を連結してupdateを呼ぶことと等しい。すなわち m.update(a); m.update(b) は m.update(a + b) と、 m << a << b は m << a + b とそれぞれ等価である。
Ruby-1.9で文字列の連結は怖いので m.update(a + b) と m << a + b と Digest::SHA256.digest(ipad + message) は避けたい。
302 Foundはわかる。リバースプロキシは何するもの?
require 'uri' require 'base64' def amazon_authenticated_query_string( host, params ) re_rfc3986_unreserved = /[^A-Za-z0-9\-_.~]/ query_string = params.to_a.sort_by{|x| x.first }.map{|key, value| URI.encode(key, re_rfc3986_unreserved) +'='+ URI.encode(value, re_rfc3986_unreserved) }.join("&") string_to_sign = <<-"STRING_TO_SIGN".gsub(/^\t\t/, '').chomp GET #{host.downcase} /onca/xml #{query_string} STRING_TO_SIGN amazon_secret_access_key = "XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX" signature = Base64.encode64( hmac_sha256( amazon_secret_access_key, string_to_sign ) ).chomp return "#{query_string}&Signature=#{URI.encode(signature, re_rfc3986_unreserved)}" end