＞秀丸マクロのいろはにほへと

# 第III部〜秀丸マクロのいろはにほへと
サブルーチンについて

## サブルーチンについて

どんな言語でプログラムするとしても、「サブルーチン」はたいへん重要な役割をはたします。秀丸マクロでも、効率的なマクロやサイズの大きなマクロを書こうとすると、やはりサブルーチンのお世話になります。ここでは、秀丸マクロのサブルーチンについて、その使い方と特徴をまとめます。この章は次の３部から成り立っています。各自の必要に応じた部分を読んでください。

- 【１】サブルーチンとは?……サブルーチンの概略
- 【２】サブルーチンの文法……基本的な文法
- 【３】サブルーチンの実例……サブルーチンの実用例

### 【１】サブルーチンとは?

まず、「サブルーチンとはどんなものなのか」を説明します。マクロ文がいくつか出てきますが、意味や文法がわからなくても心配ありません。サブルーチンの文法については次の【２】で説明します。ここでは「サブルーチンってどんなもの」なのか、何となくイメージできれば十分です。

#### サブルーチンってナニ?

  話の腰を折るようで申し訳ないんですが、そもそもサブルーチンって何ですか？

  簡単に言ってしまえば「ひとかたまりの処理をまとめたもの」というような感じで捕らえてください。マクロファイル中で何度も現れるような処理をひとつのサブルーチンとして一個所に書き、必要な時にはこれを呼び出して何度も利用しようという仕組みです。

#### サブルーチンってどんなもの?

  プログラミングの経験がない私としては、サブルーチンを使った具体的な例を示してもらわないとイマイチ、理解できないのですが…。

  いま２つの数値があり、まずこの和を求め、続いてその和を10倍します。この計算結果をダイアログボックスに表示するマクロで例を示します。原型となり得るのは次のマクロです。プログラミングの経験がない方も、それとなく何をやっているか、ある程度想像つくのではないでしょうか？

      // addten1.mac
      #num1   = 5;          // 5 を #num1 に代入する
      #num2   = 6;          // 6 を #num2 に代入する
      #wa = #num1 + #num2;  //#num1 と #num2 の和を #wa に代入する
      #ans  = #wa * 10;     //#wa に 10を掛けた値を #ans に代入する
      $mes  = str(#ans);    //数値型の #ans を文字列型 $mes に変換する
      message $mes;         //$mes を表示する
      endmacro;

  いま、２つの数値が「5と6」だけであれば、このマクロだけで十分です。ところが「7と8」、「20と30」など次々と計算させなければならないのが世の常ですね。単純に書き換えるとすると次のようになってしまいます。

      // addtenum21.mac
      #num1   = 5;           // 5 を #num1 に代入する
      #num2   = 6;           // 6 を #num2 に代入する
      #puls = #num1 + #num2; //#num1 と #num2 の和を #wa に代入する
      #ans  = #wa * 10;      //#wa に 10を掛けた値を #ans に代入する
      $mes  = str(#ans);     //数値型の #ans を文字列型 $mes に変換する
      message $mes;          //$mes を表示する

      #num1   = 7;           // 7 を #num1 に代入する
      #num2   = 8;           // 8 を #num2 に代入する
      #puls = #num1 + #num2; //#num1 と #num2 の和を #wa に代入する
      #ans  = #wa * 10;      //#wa に 10を掛けた値を #ans に代入する
      $mes  = str(#ans);     //数値型の #ans を文字列型 $mes に変換する
      message $mes;          //$mes を表示する

      #num1   = 20;           // 20 を #num1 に代入する
      #num2   = 30;           // 30 を #num2 に代入する
      #puls = #num1 + #num2;  //#num1 と #num2 の和を #wa に代入する
      #ans  = #wa * 10;       //#wa に 10を掛けた値を #ans に代入する
      $mes  = str(#ans);      //数値型の #ans を文字列型 $mes に変換する
      message $mes;           //$mes を表示する
      endmacro;

  とんでもなく長いマクロ文になってしまいましたね。これに加えて「123と456」さらに……となると何十行のマクロになることやら…。しかも、そんなに長いマクロ文を入力するとなると、不注意による誤りも発生しやすくなります。そこで、サブルーチンの登場。サブルーチンを用いると次のようにできます。

      // addten31.mac
      call sub  5, 6; //5 と 6 の場合
      call sub  7, 8; //7 と 8 の場合
      call sub 20,30; //20 と 30 の場合
      endmacro;

      sub:
          ##num1   = ##1;          //第１パラメータを ##num1 に代入する
          ##num2   = ##2;          //第２パラメータを ##num2 に代入する
          ##wa = #num1 + #num2;    //##1 と #num2 の和を ##wa に 代入する
          ##ans  = #wa * 10;       //##wa に 10を掛けた値を #ans に代入する
          $$mes  = str(#ans);      //数値型 ##ans を文字列型 $$mes に変換する
          message $$mes;           //$$mes を表示する
      return;

  「何度も同じようなマクロ文を書かないで済む」様子がなんとなく把握していただければ十分です。サブルーチンを使ってみたくなっていれば、次の【２】へ進んでみましょう(^^)

### 【２】サブルーチンの文法

秀丸エディタのサブルーチンを使うためには、ごくわずかな文法知識があれば十分です。ここでは、その「ごくわずか」な取り決めを説明します。サブルーチンに関する知識は「作り方の基本型」「引数（パラメータ）」「戻り値」の３つを押さえれば完璧です。

#### サブルーチンの概要(イメージ)

  何となくサブルーチンが解ってきました。典型的なスタイルを教えてください。

  サブルーチンを使うには「call文」「ラベル」「return文」の３つの構成要素について知る必要があります。その詳細は次からの項目で説明します。ここでは、典型的なマクロプログラムの型を紹介します。マクロ文法をわからない人も下図を見ると、マクロの動き方がそれとなく理解できると思います。

  -サブルーチンがないモデル-
  call 文がない場合は、秀丸マクロはファイルの先頭から endmacro文まで上から下へと順番良くプログラムを実行します。「ファイルの先頭から順番に１行づつプログラムを処理する」のが秀丸マクロの原則です。

      　■マクロプログラム　■プログラムの流れ（矢印）
      　　　　　…↓
      　　　　　…↓
      　　　　　…↓
      　　　　endmacro;

  -サブルーチンがあるモデル-
  一方、下図のように call 文があると、call文に続いて指定するラベルの位置に制御が移ります。下の例では「SUB_001」という名前が付いたラベルへ強制的にジャンプします。ジャンプ先のラベルに移った制御は、ここからまた上から下へと順番良くマクロ文を処理します。
  そして、「return 文」を見つけると、「callされた元の位置」へ強制的に制御を戻すようになっています。 callされた位置を常に覚えているわけです。callされた位置へ戻ると、マクロはcall文の次から処理を続けます。
  この一連の流れの中で「SUB_001:」から「return;」までの処理部分をサブルーチンと呼んでいます。

      　■マクロプログラム　■プログラムの流れ（矢印）
      　　　　　…↓
      　　　　　…↓
      　　　　　…↓
      　　　　call SUB_001;──┐
      　　　　　…　 ←────│──復帰)
      　　　　　…↓　　　　　 │　　　↑
      　　　　　…↓　　　　　 │　　　│
      　　　　endmacro; 　　　 │　　　│
      　　　　　　　　　　　　 │　　　│
      　　　　　　　　　　　　 │　　　│
      　　　　SUB_001: ←───┘　　　│
      　　　　　　〜↓　　　　　　　　 │
                                       │
      　　　　　　〜↓　　　　　　　   │
      　　　　　　〜↓　　　　　　　　 │
      　　　　return;─────────┘

  なんとなく「サブルーチン」がわかってきましたか!?　続いてサブルーチンを使うための具体的な文法をまとめます。

#### サブルーチンの作り方(call文)

  call文の使い方は？

  call文は「call」に続いて指定されるラベル名の位置へジャンプするための制御文です。制御文というと何だか難しそうですが、「制御文とはプログラムの流れを強制的に変える命令」と考えればよかったですね。 call文には「ラベル」を指定しなければいけません。もしラベルがなければ、「ジャンプ先がわからないのにジャンプしろ」と命令しているようなもの。秀丸エディタは困ってしまい、「飛び先が見つかりません」とエラー表示します。

  次の例は、「KEISAN」という名前のラベルを指定します。

      call KEISAN;

  「call」と「ラベル」の間には『半角空白を１つ以上』入れてください。半角の空白以外に「タブ」も使用できます。ただし、全角の空白では絶対にダメです！

      call KEISAN;        //1個の半角空白  <<--[○]
      call       KEISAN;  //7個の半角空白  <<--[○]
      call    KEISAN;     //タブ <<------------[○]

  次の２つは間違いが起こりやすいタイプです。

      call  KEISAN;        //2個の半角空白  <<--[○]
      call　KEISAN;        //1個の全角空白  <<--[×]

#### サブルーチンの作り方(ラベル1)

  ラベルはどうやって作るのですか？

  call文でジャンプするには「ラベル」を指定する必要がありました。その「ラベル」とは、ジャンプ先を示すための一種の名札です。名札ですから、なんらかの名前を付けなければなりません。名前を付ける規則は「半角の英数字とアンダースコア」を用いることです。さらに、ラベルの行末は他のマクロ文と違い、末尾が半角コロン「:」で終わらなければなりません。
  たとえば「sub:」「_ABC123:」などがラベルとして認識されます。
  なお、ラベルはcall文の他に「goto」文のジャンプ先としても利用できます。

#### サブルーチンの作り方(ラベル2)

  ラベルにはどんな文字が使えるのですか？

  ラベルには「半角の英数字とアンダースコア」が使えます。「sub_001:」「KEISAN:」「Kotae:」などがラベルとして使えます。ただし、大文字と小文字は区別されるので、「sub_01:」「SUB_01:」「Sub_01」はすべて異なるラベルとして認識されます。注意が必要です。
  なお、半角文字の「#@$&*+-/」などの記号文字はラベルに用いることはできません。また、半角空白もラベルの一部としては使えません。さらに漢字などの２バイト系文字も使えません。

#### サブルーチンの作り方(return 文(1))

  return 文はなぜ必要なんですか？

  一見すると return 文は何も仕事をしていないようですが、「callされた位置へ制御を戻す」という大変重要なマクロ文なのです。サブルーチンは原則的に、「ラベル」で始まって、「return文」で閉じられていなければいけません。

#### サブルーチンの作り方(return 文(2))

  return 文がなくても「エラー」になりませんでした。それでも return文は必要なのでしょうか？

  特殊な使い方をしない限り「return文は絶対必要」です。エラーにならない典型的なケースをあげておきます。
  まず、サブルーチンでマクロファイルが終わっているケースです。
  秀丸マクロはファイルの先頭から終端へ向けて１行ずつプログラムが実行されると説明しました。つまり、マクロファイルの終端を見つけると自動的にマクロの実行を終了するわけです。次のcall文で制御がサブルーチンに移ったまま終了してしまうマクロ例を示します。エラーにはなりませんが、call文から endmacro文の間の処理は実行されないので、意図したような動作は期待できないでしょう。

      　■マクロプログラム　■プログラムの流れ（矢印）
      　　　　　…↓
      　　　　　…↓
      　　　　　…↓
      　　　　call SUB_001;─────┐
      　　　　　…　　　　　　　　　 │
      　　　　　…　　　　　　　　　 │
      　　　　　…　　　　　　　　　 │
      　　　　endmacro; 　　　　　　 │
      　　　　　　　　　　　　　　　 │
      　　　　　　　　　　　　　　　 │
      　　　　SUB_001:← 呼び出し──┘
      　　　　　　〜↓
      　　　　　　〜↓
      　　　　[EOF] ファイルの終端＝マクロの終了

  次に、return文を忘れてしまって次のサブルーチンに制御が移ってしまったケースの例を示します。
  return 文は「callされた位置へ制御を戻す」機能がありますが、その「位置」とは「直前にcall された位置」です。したがって次のようなケースでは、call 文でSUB_001 にジャンプし、原則的にはラベル SUB_002 に対応する return文まで行った後で元の位置に復帰するような動きになります。この場合もエラー表示はありません。

      　■マクロプログラム　■プログラムの流れ（矢印）
      　　　　　…↓
      　　　　　…↓
      　　　　　…↓
      　　　　call SUB_001; ──┐
      　　　　　…　←─────│─────┐
                                │          │
      　　　　　…↓　　　　　  │　　　　　│
      　　　　　…↓　　　　　  │　　　　　│
      　　　　endmacro;　　　　 │　　　　　│
      　　　　　　　　　　　　  │　　　　　│
      　　　　　　　　　　　　  │　　　　　│
      　　　　SUB_001:←────┘　　　　　│
      　　　　　　〜↓　　　　　　　　　　　│
      　　　　　　〜↓　　　　　　　　　　　│
      　　　　((returnを忘れている))　　　　│
      　　　　　　〜↓　　　　　　　　　　　│
      　　　　　　〜↓　　　　　　　　　　　│
                                            │
      　　　　SUB_002:（次のサブルーチン突入│
      　　　　　　〜↓　　　　　　　　　　　│
      　　　　　　〜↓　　　　　　　　　　　│
      　　　　return; ───────────┘

  こうしたプログラム制御は「特殊なワザ」として使えるものの、プログラムとして「特殊」なことはできるだけ避けた方が良いでしょう。

#### サブルーチンの位置

  「…の飛び先がありません」というエラーメッセージが出るのですが、理由がわかりません。文法的にはまったく問題ないハズなんですが…？

  秀丸マクロのサブルーチンでは「一番最初に現れる call 文より前に return文を置くことはできない」という規則があります。これまで「ラベル」は名札みたいなものだと説明したように、ラベルそのものは何の動作もしません。したがってラベルがあってもプログラムは、「あ、ここにラベルがあるな。位置だけ覚えておこう」と了承するだけで次の行へ処理を移します。一方、return文は「呼び出し元へ戻る」動作をします。ところが、対となるラベルが一度もcall文で呼び出されていない場合には、呼び出し元が記憶されていないわけですからエラーとなります。
  下図のような場合です。SUB_001サブルーチンが呼び出されないのに、return文に到達してしまうケースです。 return文は呼び出し元へ戻ろうとしますが、この時点では「call SUB_001」が実行されていないので、 return文はどこへ戻って良いかわからないわけです。

      　■マクロプログラム　■プログラムの流れ（矢印）
      　　　　　…↓
      　　　　　…↓
      　　　　SUB_001:
      　　　　　〜↓
      　　　　　〜↓
      　　　　　〜↓
      　　　　return;→──→×　どこへ戻るか不明 => エラー表示
      　　　　　…
      　　　　　…
      　　　　call SUB_001;
      　　　　　…
      　　　　　…
      　　　　　…
      　　　　endmacro;

  ◎コラム
  ［イタズラ好きのあなたに、そっと教えます…］

  ラベルの付け方は以上の通りで、マクロヘルプに掲載されている公式見解ですが、秀まるお氏にしても、秀丸担当氏にしても、かなりの「イタズラ好き」なところがあるようです。秀丸マクロを使っている間は一瞬も気を許してはいけません(^^;。で、ラベルには「半角カタカナ」が(一応)許容されるようです。

      //sub_test21.mac
      call サブルーチン12;
      endmacro;

      サブルーチン12:

         message "aaaあああああ";

      return;

  また、「全角ひらがな」も(一応)使えてしまいます。

      // sub_test22.mac
      call あいしている;
      endmacro;

      あいしている:

          message "ボ・ボ・ぼかぁ〜、チミを……";

      return;

  さらに「漢字」混じりの場合も(一応)許容されることもあります。「愛」があれば良い、という意味ではありません、念のため(^^;

      // sub_test23.mac
      call 愛情;
      endmacro;

      愛情:

          message "愛している";

      return;

  ただし、特定の漢字や特定のバイト数であるとダメになります。ちなみに上の「愛情」というラベルを「愛情表現」にするとアウトです。サブルーチン名にどうしても漢字や全角文字を使いたい人は「ダメモト」でトライするのも楽しいかもしれません。

  もっとも「半角英数文字が基本」であることは基本原則として忘れないでください。間違っても「"愛情表現"が上手に使えないのですが」なんて質問を会議室にアップしてはいけません(^^;

#### グローバル変数とローカル変数

  サブルーチンの中では変数の頭が「$$」とか「##」になっているものがあります。これはミスタイプではないのですか？

  変数には文字列型変数と数値型変数があり、文字型変数の場合は頭に「$」が、数値型変数の場合には頭に「#」が付きます。これらは「グローバル変数」あるいは「広域変数」と呼ばれ、マクロ全体で常に有効な変数です。
  一方、「$$」や「##」で始まる変数は「ローカル変数」または「局所変数」と呼ばれ、これらが使用されているサブルーチンの中だけ有効です。呼び出し元に復帰した時点で下位のローカル変数は消滅（初期化）します。
  まず、サブルーチン内では、ローカル変数を用いるようにしましょう。次の例では、グローバル変数($str1)は、サブルーチンの中も呼び出し元に復帰してからも常に値を維持しています。サブルーチン sub1 の中で宣言されているローカル変数($$str2)は、呼び出し元に戻ると初期化されてしまいカラの文字列（文字なし）になっていることがわかります。

      // sub_rolac.mac
      $str1 = "A";
      message $str1 + $$str2;// dlg_1=>[A]
      call sub1;
      message $str1 + $$str2;// dlg_3=>[A]
      endmacro;

      sub1:
          $$str2 = "B";
          message $str1 + $$str2;// dlg_2=>[AB]
      return;

#### ローカル変数の有効範囲

  サブルーチンからさらにサブルーチンを呼び出した時、ローカル変数はどうなるのですか？

  ローカル変数は宣言されたサブルーチンの中だけで有効と説明しました。サブルーチンからさらに別のサブルーチンが呼び出された場合、呼び出されたサブルーチンのローカル変数が呼び出し元に戻った時点で初期化されますが、呼び出し元のサブルーチンのローカル変数はそのサブルーチン内では維持されます。

  次の例で、dlg_2 と dlg_4 でローカル変数 $$str2 が維持されている点に注目してください。

      // subsub.mac
      $str1 = "A";
      message $str1 + $$str2 + $$str3;// dlg_1=>[A]
      call sub1;
      message $str1 + $$str2 + $$str3;// dlg_5=>[A]
      endmacro;

      sub1:
          $$str2 = "B";
          message $str1 + $$str2 + $$str3;// dlg_2=>[AB]
          call sub2;
          message $str1 + $$str2 + $$str3;// dlg_4=>[AB]
      return;

      sub2:
          $$str3 = "C";
          message $str1 + $$str2 + $$str3;// dlg_3=>[AC]
      return;

#### サブルーチンへの引数(1)

  サブルーチンへ引数を渡せますか？

  引数を指定してサブルーチンへ値を渡せます。call文に続くラベルの後ろに引数として指定してください。

      // sub01.mac
      call mess "hanako";
      call mess "tarou";
      endmacro;

      mess:
          message "Hello ! " + $$1;
      return;

#### サブルーチンへの引数(2)

  引数はひとつしか指定できないのですか？

  カンマで区切って並べればいくつでも指定できます。また、引数をサブルーチン側で参照するには、文字列型の引数の場合は$$1, $$2, $$3…、数値型の引数の場合は ##1, ##2, ##3…とします。注意しなければいけないのは、これらの 1, 2, 3 は call文で指定した引数の並び順であることです。文字列型と数値型の引数が混在する場合は、呼び出す側とサブルーチンとの間で「型と順番」を一致させるように気をつけてください。

      // sub31.mac
      call mess "hanako",123,"hidiemaru";
      endmacro;

      mess:
          message "Hello ! " + $$1;
          message "Number is " + str(##2);
          message "I am " + $$3;
      return;

#### サブルーチンへの引数(3)

  サブルーチンへ引数がうまく引き渡せません。

  引数の「数」と「型」と「順番」を合わせる必要があります。次の例は、３つの文字列型変数と２つの数値型変数を「test」というサブルーチンへ渡しています。引数の順番に注意しましょう。特にサブルーチン側での引数の参照順に留意してください。

      call test #n1,$s1,$s2,#n2,$s3;
      //マクロ処理
      endmacro;

      test:
          ##ukenum1 = ##1;
          $$ukestr1 = $$2;
          $$ukestr2 = $$3;
          ##ukenum2 = ##4;
          $$ukestr3 = $$5;
          //サブルーチン本体
          // ...
          // ...
      return;

  上の例では、サブルーチン側の「##1」は、call文の「#n1」を参照しており、次のような関係があることを確認してください。呼び出す側とサブルーチンとの間で「型と順番」を一致させることが大切です。

      call側　サブルーチン側
      　#n1 <==> ##1
      　$s1 <==> $$2
      　$s2 <==> $$3
      　#n2 <==> ##4
      　$s3 <==> $$5

  次のように、文字列型の変数と数値型の変数を分けて考えてはいけません。

      call側　サブルーチン側
      　#n1 <==> ##1
      　$s1 <==> $$1
      　$s2 <==> $$2
      　#n2 <==> ##2
      　$s3 <==> $$3

#### サブルーチンからの戻り値(1)

  サブルーチンから戻り値を得ることができますか？

  戻り値として変数値をひとつ指定できます。サブルーチンの終わりを示す「return文」に続いて戻り値を指定します。次の例は、文字型変数 $$ansを返します。

      // subret1.mac
      $str1 = "A";
      message $str1 + $str2;// dlg_1=>[A]
      call sub1;
      $str2 = $$return;
      message $str1 + $str2;// dlg_5=>[Axxx]
      endmacro;

      sub1:
          $$ans = "xxx";
      return $$ans;

  復帰先では戻り値を文字列型であれば「$$return」、数値型であれば「##return」という変数で参照できます。
  次の例は、２つの数値型変数を「calc2Num」サブルーチンへ渡し、これを合計し、答えを数値型変数で呼び出し元へ返しています。呼び出し元ではサブルーチンでの計算結果を「##return」という数値型変数で受け取っています。

      // subcalc1.mac
      #a = 12;
      #b = 34;
      call calc2Num #a,#b;
      #ans = ##return;
      message str(#ans);// dlg =>[46]
      endmacro;

      calc2Num:
          ##n1 = ##1;
          ##n2 = ##2;
          ##wa = ##n1 + ##n2;
      return ##wa;

#### サブルーチンからの戻り値(2)

  戻り値を文字で得たいのですが。

  サブルーチンからの戻り値は数値型変数だけでなく、文字列数値型でも得られます。次の例は上の例とほぼ同じですが、戻り値を文字列型で返してします。

      // subcalc2.mac
      #a = 12;
      #b = 34;
      call calc2Num #a,#b;
      $ans = $$return;
      message $ans;// dlg =>[46]
      endmacro;

      calc2Num:
          ##n1 = ##1;
          ##n2 = ##2;
          ##wa = ##n1 + ##n2;
          $$wa = str(##wa);
      return $$wa;

#### サブルーチンからの戻り値(3)

  サブルーチンからの戻り値（##return,$$return）を新しい変数で受けるのは面倒です。

  戻り値そのものをマクロ文で利用できます。次の例は、上の例とほぼ同じですが、戻り値を新しい変数に格納しないですぐに利用してます。

      // subcalc3.mac
      #a = 12;
      #b = 34;
      call calc2Num #a,#b;
      message "合計" + $$return + "円";// dlg =>[合計46円]
      endmacro;

      calc2Num:
          ##n1 = ##1;
          ##n2 = ##2;
          ##wa = ##n1 + ##n2;
          $$wa = str(##wa);
      return $$wa;

#### サブルーチンからの戻り値(4)

  戻り値がひとつしか使えないのでは不便です。

  秀丸マクロに限らず、ほとんどの言語では戻り値は１つです。複数の戻り値を扱いたい場合は、グローバル変数を用いることで対応します。

      // subcalc4.mac
      #a = 12;
      #b = 34;
      call calc2Num #a,#b;
      message "和" + str(#wa) + " 差" + str(#sa);// dlg =>[和46 差-22]
      endmacro;

      calc2Num:
          ##n1 = ##1;
          ##n2 = ##2;
          #wa = ##n1 + ##n2;
          #sa = ##n1 - ##n2;
      return;

  一見、return 文で何も返していないようですが、数値型変数 #wa, #saはグローバル変数になっているので、これらの変数は元のルーチンでも参照できます。グローバル変数はサブルーチンで宣言されてもマクロ全域で有効です。
  こうしてグローバル変数を用いると実質的に複数の戻り値を返せます。

#### サブルーチンと関数

  サブルーチンは「ひとかたまりの処理をまとめたもの」と言うのであれば、「関数と同じ」じゃないかと思うんですが？

  仕事の内容からするとほぼ同じです。ただ、サブルーチンは「マクロの実行される行を制御」しているので、関数のようにそれ自体が「値を持つ」ことができません。

#### サブルーチンのネスト

  サブルーチンからさらに別のサブルーチンを呼び出せますか？

  できます。この操作は「サブルーチンのネスト」と呼ばれ、秀丸マクロでのサブルーチンのネストの深さは20回くらいが限界になっています。
  秀丸エディタ Ver.8.73で、ネスト深さは最大200回までに変更されています。
  秀丸エディタ Ver.9.12で再度変更され、ネスト深さは最大150回までとなっています。

#### サブルーチンのもうひとつの用途

  いろんな人の作ったマクロを眺めていると、一度しか呼び出されていないのにサブルーチンを使っている例があります。サブルーチンにする価値がないと思うんですが？

  「サブルーチンは繰り返し処理に多用される」という原則からは外れていますね。ただ、サブルーチンには「全体の流れを把握しやすくする」役割もあります。いわゆるトップダウンでマクロの骨組みを作っておき、後で細部を作り上げておくという手法です。また、こうして作られたサブルーチンは、他のマクロでも流用できるので生産性が高くなります。

#### サブルーチンの作り方(refcall)

  refcall文の使い方は？

  refcall は call と同様にサブルーチンを呼び出します(秀丸エディタ Ver.9.12以降)。

  call との大きな違いは以下になります。

  - 指定する引数は変数である必要がある。
  - 引数に指定された変数の内容を変更することができる。(サブルーチン内で変更して、呼び出し元に返すことができる)

  引数に変数以外は指定出来ないということは、`"文字列"` `数値` `式` は引数に指定する事ができません。

      NG: refcall sub "文字列";       //文字列なのでNG
      NG: refcall sub 2;              //数値なのでNG
      NG: refcall sub 2 + 1;          //式なのでNG
      NG: refcall sub #a + 1;         //式なのでNG
      NG: refcall sub $a + "文字列";  //文字列を結合する"式"になるのでNG
      OK: refcall sub $a, #n;         //引数が変数だけなのでOK

  引数に指定する変数は配列でも問題ありません。配列を指定する場合は、角括弧より前を指定してください。配列名と同じ名前の変数があった場合、角括弧がないとその変数を対象にしてしまいます。

      #num[0] = 0;
      #num[1] = 1;
      #num[2] = 2;
      #num[3] = 3;
      #num[4] = 4;
      #num[5] = 5;
      #num[6] = 6;
      #num[7] = 7;
      #num[8] = 8;
      #num[9] = 9;

      $str[0] = "str0";
      $str[1] = "str1";
      $str[2] = "str2";
      $str[3] = "str3";
      $str[4] = "str4";
      $str[5] = "str5";
      $str[6] = "str6";
      $str[7] = "str7";
      $str[8] = "str8";
      $str[9] = "str9";

      //配列と同じ名前の変数
      $str = "check!!";

      debuginfo 2;
      debuginfo "1:" + $str + "\n";
      //引数に配列を指定。変数名は角括弧より前を指定する。
      refcall debugout $str , #num;
      debuginfo "2:" + $str + "\n";

      endmacro;

      debugout:
          ##max = 10;
          ##cnt = 0;
          while(##max > ##cnt){
              //配列を呼び出す場合は、引数に角括弧をつけること。
              debuginfo sprintf("$str[%d] = %s ; #num[%d]=%d\n",##cnt, $$1[##cnt], ##cnt, ##2[##cnt]);
              ##cnt = ##cnt + 1;
          }
          //もし、配列名と同じ変数があった場合は、角括弧 をつけ忘れると、そちらを書き換えてしまう。
          $$1 = "CHECK??";
      return;

  マクロの実行結果。角括弧をつけていない場合、同名の変数の内容を書き換えている。同名の変数がない場合は新しい変数が追加されます。 (秀丸マクロには変数の宣言命令はないので、新しい変数が定義されるだけ)

  逆に配列でない変数を指定し、サブルーチン内で角括弧つきで操作した場合も同様の動作になります。 (同名配列があれば書き換え、なければ新しい配列が作られる)

      1:check!!
      $str[0] = str0 ; #num[0]=0
      $str[1] = str1 ; #num[1]=1
      $str[2] = str2 ; #num[2]=2
      $str[3] = str3 ; #num[3]=3
      $str[4] = str4 ; #num[4]=4
      $str[5] = str5 ; #num[5]=5
      $str[6] = str6 ; #num[6]=6
      $str[7] = str7 ; #num[7]=7
      $str[8] = str8 ; #num[8]=8
      $str[9] = str9 ; #num[9]=9
      2:CHECK??

  refcall で呼び出されたサブルーチンから、その引数を使って refcall する事も可能です。

  下記マクロは、配列の0〜5 までを check_sub1、それ以降を check_sub2 で変更しています。

      #num[0] = 0;
      #num[1] = 1;
      #num[2] = 2;
      #num[3] = 3;
      #num[4] = 4;
      #num[5] = 5;
      #num[6] = 6;
      #num[7] = 7;
      #num[8] = 8;
      #num[9] = 9;

      $str[0] = "str0";
      $str[1] = "str1";
      $str[2] = "str2";
      $str[3] = "str3";
      $str[4] = "str4";
      $str[5] = "str5";
      $str[6] = "str6";
      $str[7] = "str7";
      $str[8] = "str8";
      $str[9] = "str9";

      $str = "check!!";

      debuginfo 2;

      debuginfo "-1st-----------------------------\n";
      refcall debugout $str , #num;

      //引数の中身を変更
      refcall check_sub1 $str , #num;

      debuginfo "-2nd-----------------------------\n";
      refcall debugout $str , #num;

      debuginfo "-End-----------------------------\n";

      endmacro;

      debugout:
          ##max = 10;
          ##cnt = 0;
          while(##max > ##cnt){
              debuginfo sprintf("$str[%d] = %s ; #num[%d]=%d\n",##cnt, $$1[##cnt], ##cnt, ##2[##cnt]);
              ##cnt = ##cnt + 1;
          }
          debuginfo sprintf("$str = %s\n", $$1);
      return;

      check_sub1:
          ##max = 6;
          ##cnt = 0;
          while(##max > ##cnt){
              $$1[##cnt] = $$1[##cnt] + "-1";
              ##2[##cnt] = ##2[##cnt] + 100;
              ##cnt = ##cnt + 1;
          }
          //引数をそのまま別のサブルーチンに引き渡す
          refcall check_sub2 $$1, ##2;
      return;

      check_sub2:
          ##max = 10;
          ##cnt = 6;
          while(##max > ##cnt){
              $$1[##cnt] = $$1[##cnt] + "-2";
              ##2[##cnt] = ##2[##cnt] + 200;
              ##cnt = ##cnt + 1;
          }
          $$1 = "CHECK-2";
      return;

  出力結果。

      -1st-----------------------------
      $str[0] = str0 ; #num[0]=0
      $str[1] = str1 ; #num[1]=1
      $str[2] = str2 ; #num[2]=2
      $str[3] = str3 ; #num[3]=3
      $str[4] = str4 ; #num[4]=4
      $str[5] = str5 ; #num[5]=5
      $str[6] = str6 ; #num[6]=6
      $str[7] = str7 ; #num[7]=7
      $str[8] = str8 ; #num[8]=8
      $str[9] = str9 ; #num[9]=9
      $str = check!!
      -2nd-----------------------------
      $str[0] = str0-1 ; #num[0]=100
      $str[1] = str1-1 ; #num[1]=101
      $str[2] = str2-1 ; #num[2]=102
      $str[3] = str3-1 ; #num[3]=103
      $str[4] = str4-1 ; #num[4]=104
      $str[5] = str5-1 ; #num[5]=105
      $str[6] = str6-2 ; #num[6]=206
      $str[7] = str7-2 ; #num[7]=207
      $str[8] = str8-2 ; #num[8]=208
      $str[9] = str9-2 ; #num[9]=209
      $str = CHECK-2
      -End-----------------------------

### 【３】サブルーチンの実例

ここでは、いくつかのサブルーチンの例を示します。

#### 文字列変換

あくまでも、サブルーチンの作成例です。各種変換は、変換モジュールを使用すれば、以下のサブルーチンを使用しなくても可能です。

##### [半角大文字→小文字の変換]

用途
  文字列中の半角アルファベットの大文字を小文字に変換する。
引数
  $$1
    変換したい文字列
戻り値
  $$return
    変換後の文字列

    CaseToLOWER:
        ##s = strlen( $$1 );
        while( ##s > 0 ){
            ##s = ##s - 1;
            ##code = ascii( midstr( $$1, ##s, 1 ) );
            if( ##code > 64 && ##code < 91 )##code = ##code + 32;
            $$str = char( ##code ) + $$str;
        }
        return $$str;

##### [半角小文字→大文字の変換]

用途
  文字列中の半角アルファベットの小文字を大文字に変換する。
引数
  $$1
    変換したい文字列
戻り値
  $$return
    変換後の文字列

    CaseToUPPER:
        ##s = strlen( $$1 );
        while( ##s > 0 ){
            ##s = ##s - 1;
            ##code = ascii( midstr( $$1, ##s, 1 ) );
            if( ##code > 96 && ##code < 123 )##code = ##code - 32;
            $$str = char( ##code ) + $$str;
        }
        return $$str;

##### [全角→半角変換]

用途
  文字列中の全角ひらがな＆カタカナを半角カタカナに変換する。
引数
  $$1
    変換したい文字列
戻り値
  $$return
    変換後の文字列

    CaseToHANKAKU:
        ##hwnd = hidemaruhandle( 0 );
        openfile "/h ";
        ##s_hwnd = hidemaruhandle( 0 );
        insert $$1;
        selectall;
        tohankaku;
        gofileend;
        $$tmp = gettext(0,0,x,y);
        setactivehidemaru ##hwnd;
        closehidemaruforced ##s_hwnd;
        return $$tmp;

##### [半角カタカナ＆全角ひらがな(カタカナ)→全角カタカナ(ひらがな)変換]

用途
  文字列中の半角カタカナと全角ひらがな(カタカナ)を全角カタカナ(ひらがな)に変換する。
引数
  $$1
    変換したい文字列
  ##2
    = 0 の場合は全角カタカナへ変換する。
    = 1 の場合は全角ひらがなへ変換する。
戻り値
  $$return
    変換後の文字列

    CaseToZENKAKU:
        ##hwnd = hidemaruhandle( 0 );
        openfile "/h ";
        ##s_hwnd = hidemaruhandle( 0 );
        insert $$1;
        selectall;
        if( ##1 ) tozenkakuhira;
        else tozenkakukata;
        gofileend;
        $$tmp = gettext(0,0,x,y);
        setactivehidemaru ##hwnd;
        closehidemaruforced ##s_hwnd;
        return $$tmp;

#### 日付・曜日の各種フォーマットへの変換

##### [現在の日付を別々にグローバル変数にセットする]

用途
  現在の日付情報を $YEAR, $MONTH, $DATE, $WEEK にセットする。引数
  $$1:　YY/MM/DD 形式の日付 (date キーワードで取得)
戻り値
  なし($YEAR,$MONTH,$DATE,$WEEKに現在の日付がセットされている)

    GetNowDate:
        $YEAR  = leftstr( $$1, 2 );
        $MONTH = midstr( $$1, 3, 2 );
        $DATE  = midstr( $$1, 6, 2 );
        $WEEK  = midstr( $$1, 9, 2 );

        return;

(補足)
ver.3.02から、年・月・日をそれぞれ返す year, month, day などのキーワードが追加されました。但し、これらのキーワードは、マクロの実行開始時または refreshdatetime 文実行時の時刻を返します。
詳しくは、秀丸マクロヘルプの「内部的な値を表現するキーワード」-「日付と時刻関連」を参照してください。

##### [日付→和曜日変換]

用途
  指定された日付(YY/MM/DD)の曜日を調べて「日〜土」を返す。
引数
  $$1
    YY/MM/DD 形式の日付 (date キーワードで取得)
戻り値
  $$return
    得られた曜日("月"等の漢字１文字)

    DateToDay_JPN:
        if( !#DEFINED ){
            #MON[0] = 0;
            ##a = 1;
            while( ##a < 12 ){
                if( ##a==2 ) ##i = 28;
                else if( ##a==4 || ##a==6 || ##a==9 || ##a==11 )
                    ##i = 30;
                else ##i = 31;
                #MON[##a] = #MON[##a-1] + ##i;
                ##a = ##a + 1;
            }
            #DEFINED = 1;
        }

        ##y = val( leftstr($$1,4) );
        ##m = val( midstr($$1,5,2) );

        ##date = val( rightstr($$1,2) ) + #MON[##m-1] +
            (##y-1)*365 + (##y-1)/4 - (##y-1)/100 + (##y-1)/400;
        if( (!(##y%4)) && ( ##y%100 || (!(##y%400)) ) && ##m > 2 )
            ##date = ##date + 1;

        return midstr("日月火水木金土",(##date%7)*2,2);

(補足)
ver.3.02から、dayofweek キーワードが追加されました。但し、これらのキーワードは、マクロの実行開始時または refreshdatetime 文実行時の時刻を返します。
詳しくは、秀丸マクロヘルプの「内部的な値を表現するキーワード」-「日付と時刻関連」を参照してください。

##### [日付→英曜日変換]

用途
  指定された日付(YY/MM/DD)の曜日を調べて「Sun〜Satur」を返す。引数
  $$1
    YY/MM/DD 形式の日付戻り値
  $$return
    得られた曜日("Sun"等の文字列)

    DateToDay_ENG:
        if( !#DEFINED ){
            #MON[0] = 0;
            ##a = 1;
            while( ##a < 12 ){
                if( ##a==2 ) ##i = 28;
                else if( ##a==4 || ##a==6 || ##a==9 || ##a==11 )
                    ##i = 30;
                else ##i = 31;
                #MON[##a] = #MON[##a-1] + ##i;
                ##a = ##a + 1;
            }
            $DAY[0] = "Sun";
            $DAY[1] = "Mon";
            $DAY[2] = "Tues";
            $DAY[3] = "Wednes";
            $DAY[4] = "Thurs";
            $DAY[5] = "Fri";
            $DAY[6] = "Satur";
            #DEFINED = 1;
        }

        ##y = val( leftstr($$1,4) );
        ##m = val( midstr($$1,5,2) );

        ##date = val( rightstr($$1,2) ) + #MON[##m-1] +
            (##y-1)*365 + (##y-1)/4 - (##y-1)/100 + (##y-1)/400;
        if( (!(##y%4)) && ( ##y%100 || (!(##y%400)) ) && ##m > 2 )
            ##date = ##date + 1;

        return $DAY[##date%7];

#### その他

##### [指定されたセパレータで区切られた文字列を配列に格納する]

用途
  CSV フォーマットや ini ファイルのデータを切り分ける引数
  $$1
    データ文字列($$2 で区切られた文字列) $$2
    データ区切り子
戻り値
  ##return
    得られたデータの数(データそのものは $DATA[] 配列で返す… $DATA[0]〜$DATA[##return-1] まで)

    SPLIT_DATA:
        ##l = strlen( $$1 );
        ##p = strlen( $$2 );
        ##a = 0;
        while( 1 ){
            if( !##l ) break;
            ##s = strstr( $$1, $$2 );
            if( ##s == -1 ){
                $DATA[##a] = $$1;
                ##a = ##a + 1;
                break;
            }
            $DATA[##a] = leftstr( $$1, ##s );
            ##l = ##l - ##s - ##p;
            $$1 = rightstr( $$1, ##l );
            ##a = ##a + 1;
        }
        return ##a;

##### [範囲選択領域内の置換]

用途
  範囲選択した領域内で置換する
引数
  $$1
    検索文字列
  $$2
    置換文字列
  ##3
    検索オプションのフラグ(Ｑ＆Ａ参照)
戻り値
  ##return
    置換された文字列の数

備考： Ver.2.16 以降では検索系のコマンドに inselect オプション(範囲選択領域内の検索)が追加されました。

    replace_in_selected_region:
        escape;
        //  選択範囲の座標を取得
        moveto selendx, selendy;
        ##ex = column+1; ##ey = lineno;
        moveto seltopx, seltopy;
        ##tx = column+1; ##ty = lineno;

        ##selfound = getininum("hidemaru.ini","Env","SelectFound");
        writeininum "hidemaru.ini", "Env", "SelectFound", 1;

        //  前回の検索文字列、検索オプションを保存
        $$s_buf = searchbuffer; ##s_opt = searchoption;
        //  検索条件を設定
        setsearch $$1, ##3;

        ##num = 0;
        while( 1 ){
            finddown;
            if( !result ) break;
            else {
                disabledraw;
                escape;
                ##tc = column + 1;
                ##tl = lineno;
                moveto selendx, selendy;
                ##ec = column + 1;
                ##el = lineno;
                // 選択範囲を出たら終了
                if( (##el>##ey)||((##el==##ey)&&(##ec>##ex)) ) break;
                beginsel;
                movetolineno ##tc, ##tl;
                enabledraw;
                if( ##3&8 ){
                    // 置換の確認
                    question "置換しますか？";
                    if( !result ){
                        escape;
                        continue;
                    }
                }
                delete;
                insert $$2;
                ##ey = ##ey + lineno - ##el;
                ##num = ##num + 1;
            }
        }
        enabledraw;

        setsearch $$s_buf, ##s_opt; //  検索条件の復帰
        writeininum "hidemaru.ini", "Env", "SelectFound", ##selfound;

        return ##num;

ページトップ 目次に戻る