作者: Hiroshi Shinohara
日時: 2003/10/15(22:39)
  さて、前回の続きです。パイプでフィルターをつなぐようなイメージで、procedure
をつなぐには?  というお話です。

  前回のお話では、こんな風にプログラムの処理結果を連結しました。
  strg abAB 3  |  left 3      |   lineform 40  >  lineform.43l
  <strg>          <left>          <lineform>
  +----------+    +----------+    +----------+
  |データ生成| => |フィルター| => |フィルター| => 結果ファイル
  +----------+    +----------+    +----------+
                                   ↑一行バッファー
  これを一体化するには、各々のプログラムを procedureにして、
  例えば、 lineform(left(strg())))  とすれば良いのでは? と思い試してみました。
  尚、leftは組込 procedure(関数と呼びます。)に leftがありますので、そのまま
使います。

  generatorの引数に、また generatorを指定して、うまく動くかどうか不安だった
のですが、それ自体は問題なく動作します。
  しかし、処理上で1つ問題がありました。《データの末尾が分からない》 のです。

  それがどこに影響するかというと、lineformで一行分のバッファーを持っています。
  入力の単語をバッファーにつめていって、指定文字数を越える場合は、バッファー
内容を出力してから新しい単語をつめていきます。
  バッファー内容は、入力データの終わりのタイミングで、一行の指定文字数に満た
なくても、出力しないといけません。そのタイミングがつかめないのです。
  strgが最後のデータを出し終わったことが、lineformで分からないのです。

  ということで、何か細工が必要になりました。 strgか leftで、最終データの後に
何か「データ終わり」を示すデータをおまけにつけてあげれば lineformでタイミング
がつかめます。  strgや leftに直接細工をするのは 汎用的ではありませんので、
generatorの連結で細工をすることにしました。

  "a","b","c" を順に生成する generator !"abc"に もう1つのデータ "z"を追加で
発生させる例です。  単に、"|" を夾んで、並べるだけですが。
-----^ GEN04.ICN ( date:03-10-15 time:21:21 ) --------------<cut here
####################
# generatorの使い方
####################
# gen04.icn Rev.1.1 2003/10/15 風つかい
# generatorの組合せ
# This file is in the public domain.

procedure main()
            # ↓"a","b","c"を生成する generator
  every s := (!"abc" | "z") do writes(s," ")
                   # ↑ 左辺値と右辺値を生成する generator
  write()
end
-----$ GEN04.ICN ( lines:13 words:39 ) ---------------------<cut here

  結果は、最後に "z"が付加されています。
-----^ GEN04.1 ( date:03-10-15 time:21:22 ) ----------------<cut here
a b c z 
-----$ GEN04.1 ( lines:1 words:4 ) -------------------------<cut here

  「データ終わり」を示すデータとしては、通常のデータとしてはあまり使わない
&nullとしました。(空データを示すのには、良く使いますが。汗)

  という訳で、こんな感じになりました。 leftの出力の後に &nullが追加で生成
されるようにしています。一行整形 procedureは linegという名前にしています。

-----^ STRG5.ICN ( date:03-10-15 time:21:12 ) --------------<cut here
####################
# 一行整形プログラムの習作
####################
# strg5.icn Rev1.1.1 2003/10/15 風つかい
# This file is in the public domain.
link msperm  # msperm  # 文字の重複を許す 順列を生成  generator

procedure main(Larg)
  s_seed  := \Larg[1] | stop("strg5 種文字列 生成する順列の最大長 行長")
  n_char  := \Larg[2] | *s_seed   # デフォルト:種文字列の長さ
  n_limit := \Larg[3] | 79        # 一行指定長

           # ↓1行整形procedure ↓順列生成procedure     番兵↓(順列生成の終了)
  every s := lineg(n_limit,,left(strg(s_seed,n_char),n_char) | &null) do
                                                                     write(s)
                           # ↑左寄せ桁合わせ関数
end


####################
# テストデータ生成 generator
####################
# arg [1]: string   種文字列
#     [2]: integer  生成順列長の最大
# value  : string  (generator)

procedure strg(s_seed,n_char)
  /n_char := *s_seed  #  デフォルト:種文字列の長さ
           # ↓重複順列生成
  every s := msperm(s_seed,1 to n_char) do suspend s
                          # ↑数生成 1〜n_char 
end


####################
# 表示・印字用一行整形 generator
####################
# generatorから単語を読込み、区切り文字を挟んで並べ、一行指定文字数をオーバー
# したら出力する generator。 一行に単語を出来るだけ多く並べるために作成。
####################
# arg [1]: integer 一行指定文字数(画面・用紙の横幅)
#     [2]: string  区切り文字(列) デフォルト:半角スペース
#     [3]: string (generatorを指定する)  データ終了時には &nullを付加
# value  : string  表示・印字用一行文字列         # ↓番兵
# Usage  : every s := lineg(n_limit,s_delim,gen() | &null) do ..
# lineg Rev.1.1 2003/10/14 風つかい

procedure lineg(n_limit,s_delim,s_gen)
  static s_buf        # スタティック宣言(次回に呼ばれた時まで保存される)
  initial{            # 初回だけ実行
    s_buf := ""       # バッファーを空文字で初期化
  }
  /n_limit := 79      # デフォルト 一行文字数
  /s_delim := " "     # デフォルト 単語区切り文字

  # 単語の蓄積/書き出し
  if \s_gen then {    # データが終わり(&null)で無ければ

    # バッファーに新単語を足すと指定文字数を越えるなら
      # ↓ *s_bufは、s_bufのサイズを示す。(ここでは、文字列 s_bufの長さ)
    if (*s_buf + *s_delim + *s_gen) > n_limit then {
      if *s_buf > 0 then {      # バッファーが空でなければ
        suspend s_buf           # バッファー書き出し
      }
      s_buf := s_gen            # ここで 新単語がリミットより長いケースもあるが
    }                           # それは 次のサイクルで処理

    # 指定文字数を超えない(バッファーに新単語を足し込める)なら
    else {       # ↓バッファーが空(新単語が行の先頭)ならば
      s_buf ||:= if *s_buf = 0 then s_gen else s_delim || s_gen
          # ↑文字列足し仕込み                         ↑文字列の連結
    }
  } # end of if \s_gen then 

  # データが終わりなら、残り(指定文字数に満たずバッファー残っている分)を出力
  else if *s_buf > 0 then suspend s_buf

end

# strg5.icn Rev1.1.1 2003/10/15 風つかい コメント修正。
# strg5.icn Rev1.1 2003/10/14 風つかい everyを 1個に。
# strg.icn Rev1.1 2003/10/13 風つかい
-----$ STRG5.ICN ( lines:82 words:289 ) --------------------<cut here

  strg5 abAB 3 40 >strg5.43 とすると、結果はこうなります。
  前回と同じです。
-----^ STRG5.43 ( date:03-10-15 time:21:14 ) ---------------<cut here
a   b   A   B   aa  ab  aA  aB  ba  bb 
bA  bB  Aa  Ab  AA  AB  Ba  Bb  BA  BB 
aaa aab aaA aaB aba abb abA abB aAa aAb
aAA aAB aBa aBb aBA aBB baa bab baA baB
bba bbb bbA bbB bAa bAb bAA bAB bBa bBb
bBA bBB Aaa Aab AaA AaB Aba Abb AbA AbB
AAa AAb AAA AAB ABa ABb ABA ABB Baa Bab
BaA BaB Bba Bbb BbA BbB BAa BAb BAA BAB
BBa BBb BBA BBB
-----$ STRG5.43 ( lines:9 words:84 ) -----------------------<cut here

  これで、短いデータを沢山吐き出すプログラム の結果を示すのが、多少は楽に
なりそうです。

  あと、前回のプログラムも同じ問題があるはずなのに、何故動くのか? という疑問
が残ります。
  MS-DOSのパイプは、疑似パイプで、実際はテンポラリーファイルを介している。と
聞いた記憶があります。
  そこで、MS-DOS環境で動かしているので、通常のテキストファイルの読込のように、
末尾が検出できて、問題が発生しないだろう。  Unix環境では末尾データの欠けが
起きるかな。  と、想像しています。
  この辺に詳しい方に、教えて頂ければ、有り難いです。

風つかい(hshinoh@...)
IconのWWWは、  http://www.cs.arizona.edu/icon/
UniconのWWWは、http://unicon.sourceforge.net/index.html
BGM: Let It Be Me / 山見慶子 & Friends