Last update 1999/08/07
(C)平山直之
無断転載は禁止、リンクはフリー
誤字脱字の指摘は歓迎
さて、次はオブジェクトブラウザを作ってみましょう。アプリケーションのイメージはこんな感じです。
下のペインにschemeのS式を入力して評価コマンドを実行すると、上のペインにそれが図示される仕組みになっています。Vismがデータ構造を内部的にどういう形で保持しているかを視覚的に把握できるので、schemeの学習に有益であると思われます。
GUI周りは面倒なのでMFCで作ってしまうことにします。
コンソールクライアントでは、入力された文字列をそのままサーバに引き渡し、そこで得た結果をフィードバックしていただけでした。つまり、データ構造の中身については一切関知していませんでした。
しかし、オブジェクトブラウザでは、図示するために内部構造を解析する必要があります。
そこで今回は、Vismが生成したデータから実際にクライアントアプリケーションが理解できる形式のデータを取得するにはどうすればよいか、を説明します。
今回はMFCを使ったこともあり、ソース全体を見たい方にはアーカイブファイルを見てもらうことにして、ここでは重要な部分のみを抜き出して説明しようと思います。
まず、ユーザから入力を受け取り、評価する部分です。この部分についてはコンソールクライアントと特に違いはありません。
if(m_Previous!=NULL){ VIUnmark(m_VIContext,m_Previous); } VISMOBJ r=VIReadFromString(m_VIContext,s); if(r!=NULL){ // 評価 clock_t start=clock(); VISMOBJ p=VIEval(m_VIContext,r); clock_t finish=clock(); double duration = (double)(finish - start) / CLOCKS_PER_SEC; m_Previous=p; const char* emes; VERROR ecode=VIError(emes); if(ecode!=vismerr_ok){ ((CMainFrame*)AfxGetMainWnd())->ShowMessage(emes); return; } else { VIMark(m_VIContext,p); char buffer[32768]; VIWriteToString(m_VIContext,p,buffer,32767); s=buffer; return; } VIGC(m_VIContext); } return;次にこれを解析して評価する部分です。
このアプリケーションでは、解析のプロセスを
- スクロール可能領域を算出する
- 実際に描画する
の2段階に分けています。今回は手を抜いそれぞれ再帰にしたので、あまり長大なデータを表示させようとすると不正終了してしまいますが、とりあえずはこれでよしとします。
さて、こちらは1.の「スクロール可能領域を算出する」部分です。
RECT CVismbView::CalcurateTreeRect(CDC& dc,VCONTEXT context,VISMOBJ obj) { RECT r; r.left =0; r.top =0; r.right =40; r.bottom=30; switch(VIType(context,obj)){ case vismtype_other: case vismtype_nil: case vismtype_boolean: case vismtype_symbol: case vismtype_number: case vismtype_char: case vismtype_port: case vismtype_procedure: return r; case vismtype_string: { // 箱 const char* p=VIGetString(context,obj); int n=VIGetLength(context,obj); SIZE size=GetStringExtent(dc,std::string(p,n)); if(r.right<size.cx+5){ r.right=size.cx+5; } if(r.bottom<size.cy+5){ r.bottom=size.cy+5; } return r; } case vismtype_pair: { RECT r1=CalcurateTreeRect(dc,context,VIGetCar(context,obj)); RECT r2=CalcurateTreeRect(dc,context,VIGetCdr(context,obj)); r.right =std::_MAX(r.right+r1.right,r2.right); r.bottom =std::_MAX((int)r2.bottom,30)+r1.bottom; return r; } case vismtype_vector: { int n=VIGetSize(context,obj); r.right=40; r.bottom=0; for(int i=0;i<n;i++){ VISMOBJ element=VIGetElement(context,obj,i); RECT r1=CalcurateTreeRect(dc,context,element); r.right=std::_MAX( 40+(int)r1.right, (int)r1.right); r.bottom+=r1.bottom; } return r; } default: return r; } }アルゴリズム的には、
- コンスセルの場合、car部とcdr部の領域をそれぞれ算出して、それらと自分自身を(大体)足したものをその大きさとする。
- ベクタの場合、各要素の領域をそれぞれ算出して、それらと自分自身を足したものをその大きさとする。
- それ以外のオブジェクトの場合、自分自身の大きさを返す。
これらを再帰的に繰り返すものになっています。再帰を使っているため、あまり長大なデータを表示させようとすると落ちてしまいますが、スタックを増やしたり展開したりするなどして対処してください。
次に、データを表示する部分です。
void CVismbView::DrawTree(CDC& dc,VCONTEXT context,VISMOBJ obj,POINT p) { switch(VIType(context,obj)){ case vismtype_other: case vismtype_nil: case vismtype_boolean: case vismtype_symbol: case vismtype_number: case vismtype_char: case vismtype_port: case vismtype_procedure: { // 箱 RECT r; r.left =p.x+5; r.top =p.y+5; r.right =p.x+40; r.bottom=p.y+30; dc.Rectangle(&r); // 中身 r.left+=1; r.right-=1; char buffer[32768]; int size=VIWriteToString(context,obj,buffer,32768)-1; dc.DrawText(buffer,size,&r,DT_SINGLELINE|DT_CENTER|DT_VCENTER); break; } case vismtype_string: { // 箱 RECT r; r.left =p.x+5; r.top =p.y+5; r.right =p.x+40; r.bottom=p.y+30; const char* p=VIGetString(context,obj); int n=VIGetLength(context,obj); SIZE size=GetStringExtent(dc,std::string(p,n)); if(r.right-r.left<size.cx){ r.right=r.left+size.cx; } if(r.bottom-r.top<size.cy){ r.bottom=r.top+size.cy; } dc.Rectangle(&r); r.left +=2; r.top +=2; r.right -=2; r.bottom-=2; dc.DrawText(p,n,&r,DT_LEFT); break; } case vismtype_pair: { // 箱 RECT r; r.left =p.x+5; r.top =p.y+5; r.right =p.x+40; r.bottom=p.y+30; dc.Rectangle(&r); // 分割線 dc.MoveTo(p.x+5,p.y+20); dc.LineTo(p.x+40,p.y+20); dc.MoveTo(p.x+23,p.y+20); dc.LineTo(p.x+23,p.y+30); VISMOBJ car=VIGetCar(context,obj); VISMOBJ cdr=VIGetCdr(context,obj); // car部 POINT p1=p; p1.x+=40; DrawTree(dc,context,car,p1); RECT r1=CalcurateTreeRect(dc,context,car); // cdr部 POINT p2=p; p2.y+=r1.bottom; DrawTree(dc,context,cdr,p2); // リンク dc.MoveTo(p.x+15,p.y+24); dc.LineTo(p.x+45,p.y+24); dc.MoveTo(p.x+30,p.y+27); dc.LineTo(p.x+30,p2.y+5); } case vismtype_vector: { int n=VIGetSize(context,obj); RECT r; r.left =p.x+5; r.top =p.y+5; r.right =p.x+40; r.bottom =p.y+30; for(int i=0;i<n;i++){ // 要素 VISMOBJ element=VIGetElement(context,obj,i); POINT p1; p1.x =p.x+40; p1.y =r.top-5; DrawTree(dc,context,element,p1); RECT r1=CalcurateTreeRect(dc,context,element); // 箱 r.bottom=r.top+25; dc.Rectangle(&r); // インデックス CFont font; font.CreatePointFont(100,"MS ゴシック",NULL); CFont* old_font=dc.SelectObject(&font); char buffer[256]; sprintf(buffer,"[%d]",i); dc.DrawText(buffer,strlen(buffer),&r,DT_SINGLELINE|DT_CENTER|DT_VCENTER); dc.SelectObject(old_font); // リンク dc.MoveTo(r.right,r.top+18); dc.LineTo(r.right+5,r.top+18); // 区切り if(i!=n-1){ CBrush brush; brush.CreateStockObject(GRAY_BRUSH); CBrush* old_brush=dc.SelectObject(&brush); r.bottom=r.top+std::_MAX((int)30,(int)r1.bottom); r.top=r.top+25; dc.Rectangle(&r); dc.SelectObject(old_brush); } r.top=r.bottom; } } default:; } }アルゴリズム的には領域の算出と似たようなものです。領域の算出のために内部的に
CalcurateTreeRect
を呼び出しています。さて、実際にデータを取得するにはいったいどうしたらよいのでしょうか。
上記2つの関数を見ればわかると思うのですが、基本的には値を取り出したいオブジェクトを引数に指定して、VismDLL.hファイルで「primitive accessor」の項に分類されているAPIを呼び出せばよいだけです。
たとえば、
VISMOBJ
型のハンドル変数obj
が指しているのがscheme整数であることがわかっている場合、int n=VIGetInteger(context,obj);といった操作でその整数としての値を取得することができます。同様に、文字列である場合は、
const char* p=VIGetString(context,obj);とすればOKです。簡単ですね。
「ペア」「ベクタ」といったデータ構造からも、同じようなやり方で要素を取得できます。戻り値がまた
VISMOBJ
ハンドルであるのが違うくらいでしょう。実際に要素からCデータを取得したい場合は、要素の取得APIを利用して得た要素のハンドルを使ってプリミティブ操作APIを呼び出してください。schemeでは「ペア」「ベクタ」などの構造類には型がありませんので、要素の型がなんであるかはプログラマが管理しなければなりません。実際には、それぞれこのように使います。
ペア(リスト):
VISMOBJ p=xxx; // ペアの取得 for(;VIIsPair(context,p);p=VIGetCdr(context,p)){ VISMOBJ element=VIGetCar(context,p); // 処理 }ベクタ:
VISMOBJ p=xxx; // ベクタの取得 int n=VIGetSize(context,p); for(int i=0;i<n;i++){ VISMOBJ element=VIGetElement(context,p,i); // 処理 }特に難しいことはないと思います。このとき、実際はscheme整数であるオブジェクトに文字列取得APIを実行してしまったりすると、内部エラーになります。エラー処理のやり方は前回と同じです。
schemeデータからCデータを取得するAPIの他に、
- オブジェクトの動的な型を取得する関数(
VIType
)- オブジェクトが○○であるかどうか(真偽値)を取得する関数(predicate)
が用意してあります。ハンドルが指すオブジェクトが実際になんであるかが分かっていない場合には、このオブジェクトブラウザのようにこれらのAPIを適宜利用してください。
どうでしょうか。これを読んで、使ってみる気になったでしょうか?
schemeについてある程度わかっているなら、特に難しいことはなかったと思います。
スクリプトシステムが欲しくなったら、ぜひ使ってみてください。できる限りサポートするつもりでいます。
オブジェクトブラウザのソース・バイナリは前回のアーカイブに含まれていますので、それを利用してください。
おっとっと、ブラウザの操作方法を忘れてました。ctrl+Aで全選択、ctrl+Eで選択部分を評価です。
(C) 1998 Naoyuki Hirayama. All rights reserved.