[ top ] [ prev ] [ up ] [ next ]

実用的なDelphiパーサを作る

紆余曲折を経て現在は Ruby で作成している。
後で Coco/R, Delphi にも移植したい。

Download
関連

Delphiパーサの必要性

Apolloでは、拡張ライブラリの作成は、基本的に手書きである。
publishedプロパティは自動的にRubyメソッドとして登録できるようになったが、イベントハンドラやメソッドは、今のところ、DelphiヘルプやDelphiソースコード、そして既存の拡張ライブラリコードを比較検討しながら、書き進める以外に方法がない。これは大変な手間であるし、新たなDelphiクラスをApolloから使いたいときには、その度に一から書くはめになる。このように拡張性が悪いとApolloを有効に使いにくい。
これを自動化するには、Delphiソースコードの解析が必要になる。つまりDelphiパーサが必要になる。

一般的なDelphiパーサ

Delphiパーサの有名どころとしては、次のものがある。
しかし、まともに使えるものがない。というわけで、自分で作ることになった。

目標

  TEdit.Font
と入力すると
    TControl = class(TComponent)
    protected
      property Font: TFont read FFont write SetFont stored IsFontStored;
    end;

    TWinControl = class(TControl)
    end;

    TCustomEdit = class(TWinControl)
    end;

    TEdit = class(TCustomEdit)
    published
      property Font;
    end;
と出力するプログラムを書く。
入力したクラス名とメソッド名(プロパティやイベントハンドラ、そして変数フィールドも含めたいので、Delphi的にはフィールド名となる)を元に、クラス継承関係(インターフェイスも含む)とアクセス指定子(Rubyでいうところの呼び出し制限)の推移を明らかにしたいわけである。

想定 API

dp_classname = 'TEdit'
dp_fieldname = 'Font'
TEditの継承関係を知るには、TEditの親クラスが判ればよい。継承関係だけでいいならRTTIでもいける。
しかし、今回はprivate, protected, publicな情報も必要だ。RTTIにはpublishedな情報以外は残っていないという限界がある。だからソースコードを解析する道を進む。ほんとうは *.dcu を解析できたら言うことなしなのだが。

TEditが定義されているユニットを知る必要がある。
クラスが定義されているユニットはどうやったら知ることができるのだろうか。見えている(パス上の)全てのユニットを検索する以外に方法がないように思う。
dp_unitnames

classunit('TEdit') == 'StdCtrls'
ユニットソースコードがあるフォルダを知る必要がある。
dcc32.cfg には *.dcu の位置を示す情報はあるが、ソースコードの位置を示す情報はない。$(Delphi)/Source で固定するか、どこかで指定してもらうか。
unitdir('StdCtrls') == 'D:/Borland/Delphi6/Source/Vcl'
ユニットファイル名は次のようになる。
unitfname = "#{unitdir(unitname)}/#{unitname}.pas"
解析を始める。
パーサの API を考えていかなければならない。
ユニットコード全てを構造体に押し込んでも役に立たないと思われる。
型宣言ごとにイベントハンドラを呼び出すことにしよう。
dp = Delphi::Parser.new
dp.load(unitfname)

dp.on_type_decl = proc do |dp_type|
  # dp_type.class == Delphi::Type
  p dp_type.ident
  dp_class = dp_type.value
  p dp_class.herit
  dp_class.field_list.each do |visib, field|
    p visib
    dp_field = field
    p dp_field.ident
    p dp_field.param_list
    p dp_field.type
  end
end
得られる構造体は次のようになる(希望)。
  class Type
    attr_accessor :ident, :value, :directive
    ...
  end

:ident == 'TEdit'
:value == Delphi::Class.instance

  class Class
    attr_reader :herit, :field_list
    ...
  end

:herit == ['TCustomEdit']
:field_list == [['published', Delphi::Property.instance], ...]

  class Property
    attr_accessor :ident, :param_list, :type
  end

:ident == 'Font'
:param_list == nil
:type == nil

expr の表現方法

とりあえず宣言部は問題ないはずだが、式(expr)はどういう構造で表現すれば扱いやすいだろう。安易に配列の入れ子で返すと expr - term - factor - value は [[[value]]] になってしまう。これは避けたい。
Lisp 的な構造はどうだろう。
例えば
S := (Obj as TEdit).Text + #10#13;
これはこうなる。
(:= S (+ (. (as Obj TEdit) Text) #10 #13))
Ruby の配列なら:
[":=", "S", ["+", [".", ["as", "Obj", "TEdit"], "Text"], "#10", "#13"]]
一意に元に戻せたら(1対1対応できる形式なら)いいわけだ。 [ top ] [ prev ] [ up ] [ next ]