AvalonEditのソースを読んでみた

Azukiの内部構造がグチャグチャ感があるので、整理したい。しかしAzukiの開発はクラスの数を増やさないというアプローチで進めていたため今になってモジュールがこんがらがっており、おいそれと試行錯誤できない状態になってしまった。そこで、細かくクラス分けしてあるAvalonEditのソースを読むことで、より良い構造を勉強することにした。特に興味があるのは選択状態の保持方法、および折り返し表示モードにおける折り返し位置の管理方法あたり。WPFは素人同然なので苦労したけれど、選択範囲の保持・管理の方法については理解できたのでメモがてらに記録しておく。

続きを読む “AvalonEditのソースを読んでみた”

関数は「どう呼ばれうるのか」も考えて仕様を設計すると良い

問題。文字列の指定位置にあるマルチバイト文字が何バイトで構成されているか判定する関数を考える。その関数の引数として、与える文字列の長さに等しい値を、判定すべき文字の位置(インデックス)として指定した場合、この関数仕様は以下いずれが望ましいか。

  1. 成功とし、0バイトと判定する
  2. 例外(対象が見つからない)とする
  3. 例外(引数不正)とする

続きを読む “関数は「どう呼ばれうるのか」も考えて仕様を設計すると良い”

コーディングパターン: ループ条件の定石を覆して良いことも

昔のメモを見つけたので投稿しておきます。 手続き的なプログラミング言語ではループをよく書くと思いますが、大半は定型パターンがあります。たとえば配列のようなものから特定の条件を満たすものを検索する場合、普通は配列の要素を列 … 続きを読む コーディングパターン: ループ条件の定石を覆して良いことも

「ペンをカスタマイズする人はいない。ペンの使い方をカスタマイズするのだ」

電子書類の整理をしていたところ古い読書メモが出てきました。何かの縁ということで、掲載。2009-05-17 16:29 A.D.ノーマン著「エモーショナルデザイン」より: ペンをカスタマイズする人はいない。ペンの使い方を … 続きを読む 「ペンをカスタマイズする人はいない。ペンの使い方をカスタマイズするのだ」

正規表現の「^」記号とマッチング範囲

はじめに

(2011/4/6追記。本件はMicrosoftから将来のリリースで修正するとの回答がありました)
(2011/8/27追記。最新版のVisual Studio 2010+最新の.NET Framework 4で試したところ、まだ修正されていないようです。)

本日、各言語での正規表現エンジンを使って「^」記号(文字列または行の先頭を示す、アンカーあるいはゼロ幅アサーション…と呼ぶらしい)に関する動作を調査しました。背景にあるのはAzukiが内蔵している正規表現検索で行頭マッチングが行われないというユーザ報告の不具合(に近いが対策できず黙認していた動作仕様)です。過去に検索機能を実装した際の調査結果では.NETの正規表現エンジンで「Multilineモード」を有効にしても各行の先頭でない位置でマッチする現象が起こり、使えないと判断しました。今回は、この問題を改めて少し掘り下げて調査した結果報告(?)となります。

調査結果のポイントです。

  1. Microsoftの.NETが提供する正規表現エンジンでは、マッチング範囲の終了位置を指定すると「^」が常に「マッチング範囲の開始点」にマッチしてしまう(終了位置を指定しなければ常にはマッチしない)
  2. MSDNの「^」記号の説明Regex.Matchメソッドの説明からは「^」が文字列の先頭でも行頭でもない箇所にマッチする動作は予想しにくい上に、Regex.Matchのオーバーロード間での動作仕様の統一性が失われている
  3. Java (1.5以降)の正規表現エンジンでは「Anchoring Bounds」という概念で「^」の扱いをカスタマイズできる(→ Matcherクラスのリファレンス)が、.NETでは同等の機構が無い
  4. ユーザ指定の正規表現を使うアプリケーションで、特定のマッチング範囲を絞り、「^」をマッチング範囲の開始点にマッチさせたくない場合、実現できないと思われる

ポイント1および2です。Regex.Match( string, int )のオーバーロードを使ってマッチング開始点だけを指定した場合には、行頭でも文字列先頭でもない位置がマッチング範囲の開始点であっても「^」記号は該当位置にマッチしません。しかしRegex.Match( string, int, int )のオーバーロードを使うと、マッチします。したがってオーバーロード引数を追加するだけで動作仕様が変化してしまうため、不自然な印象を受けます。次に例を記します。

string text = "abc";
new Regex( "^[a-z]" ).Match( text, 1 ); // どこにもマッチしない
new Regex( "^[a-z]" ).Match( text, 1, text.Length ); // 1文字目のbでマッチする

ポイント3です。Javaの正規表現エンジンでは「Anchoring Bounds」という概念があり、「^」記号の扱いをカスタマイズできます。Anchoring Boundsを使うよう設定すると、「^」記号および「$」記号のマッチング時にマッチング範囲の前後が考慮されない — つまり問答無用でマッチング範囲の始点に「^」記号がマッチするようになります。そしてAnchoring Boundsを使わないよう設定すると、マッチング範囲の開始点が文字列先頭あるいは行頭でない限り、「^」記号はマッチング範囲の開始点にマッチしません。このように「^」記号のマッチング動作を明示的に指定できる機構があれば、それを使うことで問題回避できますが、残念ながら.NETにはありません。

ポイント4です。どうやら.NETの正規表現エンジンではJavaでいう「Anchoring Bounds」の扱いがRegex.Matchのオーバーロードごとに異なっており、Regex.Match(string,int)はAnchoring Boundsを使わず、Regex.Match(string,int,int)はAnchoring Boundsを使う動作となっています。ここで、もしAnchoring Boundsを「常に使いたい」場合は外部でマッチング対象の文字列をSubstringすることで代替できます。しかしAnchoring Boundsを「常に使いたくない」場合、Regexクラスの外部でこれを実現する方法は無いように思われます。

この仕様はAPIからもドキュメントの記述内容からも想定できるものでなく、また統一が取れていないという点も考えると、意図的な設計結果とは思えません。本件は、改めてMicrosoft社に報告と確認をしておこうと考えています。

以下、各言語・環境での検証コードおよび検証結果を記します。なお言うべきことはすでに記したので、細かい説明はしません。興味のある方や再現してみたい気分になった方へ向けた情報です。

続きを読む “正規表現の「^」記号とマッチング範囲”

丸投げ

ソフトウェアは設計上の階層構造を持つことが多く、ほぼ間違いなく最上位をアプリ層(そのアプリ固有の処理)とした複数の階層を重ねた形になっています。ごく単純なアプリの場合は最上位のアプリ層のみが存在すると解釈すれば、やはり階層構造です。このとき、特に最初が単純な一階層のアプリだったものに機能を追加していく場合、ある程度の規模を越えると階層構造を組まないと管理不可能になっていきます。本日はこの階層構造と「丸投げ」について思うことを書きます。
続きを読む “丸投げ”