2013年9月18日水曜日

Lex for Delphi(4)

考えを整理するために、修正すべきコードwriteccを貼り付けます。
  function charStr(c: char): string;
  begin
    case c of
      #0..#31,     (* nonprintable characters *)
      #127..#255: Result := '#' + intStr(Ord(c));
      '''': Result := '''''''''';
      else  Result := '''' + c + '''';
    end;
  end(*charStr*);

  procedure writecc(var f: Text; cc: CClass);
  (* print the given character class *)
  const
    MaxChar = #255;
  var
    c1, c2: char;
    col:    integer;
    tag:    string;
    Quit:   boolean;
  begin
    Write(f, '[ ');
    col  := 0;
    c1   := chr(0);
    Quit := False;
    while not Quit do
    begin
      if c1 in cc then
      begin
        if col > 0 then
        begin
          Write(f, ',');
          Inc(col);
        end;
        if col > 40 then
          { insert line break }
        begin
          writeln(f);
          Write(f, ' ': 12);
          col := 0;
        end;
        c2 := c1;
        while (c2 < MaxChar) and (succ(c2) in cc) do
          c2 := succ(c2);
        if c1 = c2 then
          tag := charStr(c1)
        else if c2 = succ(c1) then
          tag := charStr(c1) + ',' + charStr(c2)
        else
          tag := charStr(c1) + '..' + charStr(c2);
        Write(f, tag);
        col := col + length(tag);
        c1  := c2;
      end;
      Quit := c1 = MaxChar;
      if not Quit then
        c1 := Succ(c1);
    end; { of while }
    Write(f, ' ]');
  end(*writecc*);

引数ccで渡された集合型の文字を、印字可能なものならばそのまま出力、印字できない文字ならば数値化しています。 文字の範囲を0から255のコードを前提とした実装です。ASCIIで連続したコード値であれば、'A'...'Z'の形式で出力します。

実装でわかっていないのは、字句定義で"\t"としたときに、引数ccにはどのようなコード値で表されるかです。集合型から文字列に変えるのですが、ASCIIのコード値(#9)は、そのままUnicodeのコード値と同じで良いのか?BOMとか、非表示の文字を読み込んだらどうなるのか?さっぱりわかっていません。週末にでも、テストコードを書いて検証します。

2013年9月16日月曜日

Lex for Delphi(3)

連休の合間を使って字句解析のプログラム(dlex.exe)を調べています。
文字を保持する型が集合型(CClass)なので、Unicodeの文字を格納することができません。ここは、安直にString型に置き換えてみます。問題なのが、以下の部分です。

  • 集合型の演算(和集合、差集合、積集合)
  • 定数の連続値(1...128)

集合型であれば要素の順番に気を配る必要はありません。しかし、文字列に置き換えると、重複を除く必要があります。また、集合型であれば、#1...#128と短く書けますが、文字列で扱うとなると記述が長くなり、文字列定数の長さ制限に引っかかります。

騙しだましにプログラムを直して字句解析のパーサ(dlex.exe)をビルドして、サンプルの字句解析プログラムを生成してみました。しかし、サンプルとして出力されたコードと比較すると、微妙に差が出ています。惜しい!


文字列の重複を排除するためにTStringListを使っていますが、英字の大文字/小文字を無視しているように見えます。CaseSensitiveのプロパティをTrueにして、まだコード生成、比較をします。これで差異を生じている部分は残り2箇所になりました。



さらに、Unicodeの字句定義が通るか検証します。exprlex,lを修正して、指数表現の[eE]の部分をひらがなの”あ”に置き換えます。


dlex.exeで字句定義からコードを生成します。


字句定義を修正する前後で、コードと比較します。 右側の集合型の定数部部分にひらがな”あ”の文字がありません。うまくすれば、"あ"の文字が入るかと思いましたが、いずれにしろ集合型だと日本語の判定は正しく判定されないので、これで当面はOKです。




残りの作業を整理します。
  • コード生成でcc:の集合型の部分を出力している箇所を突き止める。
  • サンプルの出力コードと一致するように調整する。
  • 出力されるコードを集合型から文字列に直す。
  • 文字コードの判定で#128...#255までの指定が意味あるか調べる。

続きはまた来週

2013年9月15日日曜日

Lex for Delphi(2)

字句解析のプログラム生成を検討し始めたばかりで、考えを整理します。生成されたコードを改変して動きを検証した後は、コードを生成するツールの直し方を考えます。生成されたコードと同じく課題は集合型で実装されたコードです。字句の情報を保持するlexbase.pasの中で、修正すべき部分を示します。
type
  (* String and character class pointers: *)
  StrPtr    = PString;
  CClass    = set of char;
  CClassPtr = ^CClass;

この集合型を使うと重複無く文字を足し引き出来るのが利点です。dlex.exeの実装を調べると、集合型を使った演算がいくつかあります。

  • 和集合(+)
  • 差集合(-)
  • 積集合(*)

しかしながら、Unicodeの文字を保持できない。型CClassをString型に変えて、参照、操作の仕方を片っ端から変えればいけるのか試してみます。

Lex for Delphi(1)

ぼちぼち、Delphiで使える字句解析のコードが必要になってきました。ツールを使っていくうちに、処理の遅さが気になってきました。原因パターンマッチの遅さです。このパターンをコンパイルして速度を稼ぐために、字句解析のプログラムが必要になってきたのです。

調べてみると、Unicode未対応のプログラム(dyacclex-1.4)を見つけました。これを自分で直せないか思案しています。 いきなりLexのコードを修正するまえに、出力された字句解析のコードをどのように変えたら、Unicode対応できるか考えます

。課題は集合型で実装している部分です。集合型で文字のパターンを保持すると、Unicodeの文字列はcharに切り捨てられてしまいます。Lexが生成しコードのうち、判定の文字列を保持するレコード型を示します。

type YYTRec = record
                cc : set of char;
                s  : Integer;
              end;

ccの型を集合型から文字列に変え、ccを参照するときの方法を変えてあげればUnicode対応のコードになりそうだ。

type YYTRec = record
                cc : String;
                s  : Integer;
              end;

検証するためにサンプルプログラムを改変しました。サンプルプログラム(expr.exe)は数値を入力して、小数点の形式で数値を表示するプログラムです。指数表現1.25E+3のEの文字を無理くり、”あ”に変えます。つまり、1.25あ+3と入力します。"あ"には特別な意味はありません。ただ生成された字句解析のコードで、指数表現の部分が簡単に特定できたからです。

試してみると、字句解析はすんなり通りました。字句が切り出せて、コード値が帰ればOKです。実装がうまい具合に1つのモジュールに閉じていて、型と判定の修正だけでうまくいきます。

次なる課題は、字句解析のコードを生成するプログラムの方です。これも集合型を使っているため、Unicodeの文字はbyte charに縮小されてしまいます。

2013年8月15日木曜日

データセットのI/Oを補足するコード(私家版#2)

これ自分用のコードです。

function libname(dsn)
{
        i = index(dsn, ".");
        if (i > 0) {
                tmp = substr(dsn, 1, i - 1);
        }
        else {
                tmp = "WORK";
        }
        return toupper(tmp);
}

function memname(dsn)
{
        i = index(dsn, ".");
        if (i > 0) {
                tmp = substr(dsn, i + 1, length(dsn));
        }
        else {
                tmp = dsn;
        }
        return toupper(tmp);
}

function ilog(path, dsn)
{
        printf("R,%s,%s,%d,%s,%d\n", path, dsn, FNR, FILENAME, NR);
        return 1;
}

function olog(path, dsn)
{
        printf("W,%s,%s,%d,%s,%d\n", path, dsn, FNR, FILENAME, NR);
        return 1;
}

/NOTE: ライブラリ参照名/ && /を次のように割り当てました。/ {
        libref = $3;
        getline;
        while ($1 != "物理名") {
                getline;
        }
        lib[libref] = $3;

}

$1 ~ /NOTE:/ && $2 ~ /データセット/ && $NF ~ /オブザベーションを読み込みました。/ {
        dsn = $3;
        libref = libname(dsn);
        mem = memname(dsn);

        if (libref != "WORK" && libref != "SASHELP") {
                dir = lib[libref];
                if (dir != "") {
                        tmp = sprintf("%s/%s.sas7bdat", dir, tolower(mem));
                        status = ilog(tmp, dsn);
                }
                else {
                        printf("ERROR %s\n", dsn);
                        exit;
                }
        }
}

$1 ~ /NOTE:/ && $2 ~ /データセット/ && $NF ~ /変数です。/ {
        dsn = $3;
        libref = libname(dsn);
        mem = memname(dsn);

        if (libref != "WORK" && libref != "SASHELP") {
                dir = lib[libref];
                if (dir != "") {
                        tmp = sprintf("%s/%s.sas7bdat", dir, tolower(mem));
                        status = olog(tmp, dsn);
                }
                else {
                        printf("ERROR %s\n", dsn);
                        exit;
                }
        }
}

$1 ~ /MPRINT\([A-Z0-9_]+\):/ && $2 ~ /^set$/ {
        match($3, /([a-zA-Z0-9_]+\.)?[a-zA-Z0-9_]+/);
        if (RLENGTH > 0) {
                dsn = substr($3, RSTART, RLENGTH);
                libref = libname(dsn);
                mem = memname(dsn);
                if (libref != "WORK" && libref != "SASHELP") {
                        dir = lib[libref];
                        if (dir != "") {
                                tmp = sprintf("%s/%s.sas7bdat", dir, tolower(mem));
                                status = ilog(tmp, dsn);
                        }
                        else {
                                printf("ERROR %s\n", dsn);
                                exit;
                        }
                }
        }
}

END {
        print "_EOF_";
}

SASデータセットのI/Oを捕捉するコード(私家版)

SASログからI/Oを追いかける私家版のSASプログラム。入力のCSVファイルはAWKで作成しています。お客様先で大量のSASログから、入力と出力を特定するためのものです。入力ファイルで1件もデータを読まないで、僅かにMPRINTのログにしか手掛かりがないものが厄介です。
その他に、SASHELP.VTABLEを通してファイルの存在チェックをしているものなど、SASログには現れない入力があります。

/*
*++
* import io log-file
*--
*/

data iods;
 infile "c:\temp\iods.csv" dsd firstobs=2;
 attrib RW length=$1;
 attrib FILENAME length=$64;
 attrib DSN length=$32;
 attrib FNR length=8;
 attrib LOG length=$61;
 attrib NR length=8;
 input rw filename dsn fnr log nr;
run;

/*
*++
* input data-set
*--
*/

proc sort data=iods out=ids;
 by filename nr rw;
run;

data ids;
 set ids;
 by filename nr rw;
 if first.filename and rw eq 'R' then output;
run;

proc sort data=ids;
 by nr;
run;


/*
*++
* output data-set
*--
*/

proc sort data=iods out=ods;
 where rw eq 'W';
 by filename nr;
run;

data ods;
 set ods;
 by filename nr;
 if last.filename then output;
run;

proc sort data=ods;
 by nr;
run;

/*
*++
* make compare procedure statement
*--
*/

data _null_;
 set ods;
 attrib comment length=$256;
 attrib libstat length=$256;
 attrib titstat length=$256;
 attrib comproc length=$256;
 attrib mem length=$32;

 basedir = "/sumdata/bk02";

 comment = "/* dsn=" || compress(upcase(dsn)) || " logfile=" || compress(log) || " line=" || compress(put(fnr, best.)) || " */";
 put comment;

 i = 1;
 imax = length(filename);
 pos = 0;

 do i=1 to imax;
  if substr(filename, i, 1) eq '/' then pos = i;
 end;

 put "options nocenter;";
 libstat = "libname tmp1 '" || compress(basedir) || substr(filename, 1, pos-1) || "' access=readonly;";
 put libstat;

 libstat = "libname tmp2 '" || substr(filename, 1, pos-1) || "' access=readonly;";
 put libstat;

 titstat = "title 'dsn=" || compress(upcase(dsn)) || " log=" || compress(log) || "';";
 put titstat; 

 mem = substr(dsn, index(dsn, ".")+1);
 comproc = "proc compare base=tmp1." || compress(mem) || " compare=tmp2." || compress(mem) || " maxprint=5;";
 put comproc;
 put "run;";
 put "title;";
 put ;

run;

2013年5月3日金曜日

TExcelApplicationでExcelの書式設定を行う

Officeを2007から2013にアップグレードしたため、いくつかのアプリケーションを点検しています。そんな中で、TExcelApplicationを使ったサンプルを見つけ、Office2013で動くか試してみました。問題なく動いています。

元ネタは、Delphi-Fanの「Excelの書式設定を行う」を利用させていただきました。Excel絡みのアプリケーションを組むことがあるので、大変勉強になるコードです。ブログにコメントが残せないので、ここでお礼申し上げます。