どんな言語でプログラムするとしても、「サブルーチン」はたいへん重要な役割をはたします。 秀丸マクロでも、効率的なマクロや、サイズの大きなマクロを書こうとすると、やはりサブルーチンのお世話になります。 ここでは、秀丸マクロのサブルーチンについて、その使い方と特徴をまとめます。 この章は次の3部から成り立っています。各自の必要に応じた部分を読んでください。
■[1]サブルーチンとは?……サブルーチンの概略
■[2]サブルーチンの文法……基本的な文法
■[3]サブルーチンの実例……サブルーチンの実用例
まず、「サブルーチンとはどんなものなのか」を説明します。 マクロ文がいくつか出てきますが、意味や文法がわからなくても心配ありません。 サブルーチンの文法については次の[2]で説明します。 ここでは「サブルーチンってどんなもの」なのか、何となくイメージできれば十分です。
いま2つの数値があり、まずこの和を求め、続いてその和を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;
いま、2つの数値が「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; //第1パラメータを ##num1 に代入する
##num2 = ##2; //第2パラメータを ##num2 に代入する
##wa = #num1 + #num2; //##1 と #num2 の和を ##wa に 代入する
##ans = #wa * 10; //##wa に 10を掛けた値を #ans に代入する
$$mes = str(#ans); //数値型 ##ans を文字列型 $$mes に変換する
message $$mes; //$$mes を表示する
return;
「何度も同じようなマクロ文を書かないで済む」様子がなんとなく把握していただければ十分です。 サブルーチンを使ってみたくなっていれば、次の[2]へ進んでみましょう(^^)
秀丸エディタのサブルーチンを使うためには、ごくわずかな文法知識があれば十分です。 ここでは、その「ごくわずか」な取り決めを説明します。サブルーチンに関する知識は「作り方の基本型」、「引数(パラメータ)」、 「戻り値」の3つを押さえれば完璧です。
サブルーチンを使うには「call文」「ラベル」「return文」の3つの構成要素について知る必要があります。 その詳細は次からの項目で説明します。 ここでは、典型的なマクロプログラムの型を紹介します。 マクロ文法を知らない人も下図を見ると、マクロの動き方がそれとなく理解できると思います。
-サブルーチンがないモデル-
call 文がない場合は、秀丸マクロはファイルの先頭から endmacro文まで上から下へと順番良くプログラムを実行します。 「ファイルの先頭から順番に1行づつプログラムを処理する」のが秀丸マクロの原則です。
■マクロプログラム ■プログラムの流れ(矢印)
…↓
…↓
…↓
endmacro;
-サブルーチンがあるモデル-
一方、下図のように call 文があると、call文に続いて指定するラベルの位置に制御が移ります。 下の例では「SUB_001」という名前が付いたラベルへ強制的にジャンプします。 ジャンプ先のラベルに移った制御は、ここからまた上から下へと順番良くマクロ文を処理します。
そして、「return 文」を見つけると、「callされた元の位置」へ強制的に制御を戻すようになっています。 callされた位置を常に覚えているわけです。callされた位置へ戻ると、マクロはcall文の次から処理を続けます。
この一連の流れの中で「SUB_001:」から「return;」までの処理部分をサブルーチンと呼んでいます。
■マクロプログラム ■プログラムの流れ(矢印)
…↓
…↓
…↓
call SUB_001;──┐
… ←────│──復帰)
…↓ │ ↑
…↓ │ │
endmacro; │ │
│ │
│ │
SUB_001: ←───┘ │
〜↓ │
│
〜↓ │
〜↓ │
return;─────────┘
なんとなく「サブルーチン」がわかってきましたか!? 続いてサブルーチンを使うための具体的な文法をまとめます。
call文は「call」に続いて指定されるラベル名の位置へジャンプするための制御文です。 制御文というと何だか難しそうですが、「制御文とはプログラムの流れを強制的に変える命令」と考えればよかったですね。 call文には「ラベル」を指定しなければいけません。 もしラベルがなければ、「ジャンプ先がわからないのにジャンプしろ」と命令しているようなもの。 秀丸エディタは困ってしまい、「飛び先が見つかりません」とエラー表示します。
次の例は、「KEISAN」という名前のラベルを指定します。call KEISAN;
call KEISAN; //1個の半角空白 <<--[○]
call KEISAN; //7個の半角空白 <<--[○]
call KEISAN; //タブ <<------------[○]
call KEISAN; //2個の半角空白 <<--[○]
call KEISAN; //1個の全角空白 <<--[×]
特殊な使い方をしない限り「return文は絶対必要」です。エラーにならない典型的なケースをあげておきます。
まず、サブルーチンでマクロファイルが終わっているケースです。
秀丸マクロはファイルの先頭から終端へ向けて1行ずつプログラムが実行されると説明しました。 つまり、マクロファイルの終端を見つけると自動的にマクロの実行を終了するわけです。 次の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;
引数を指定してサブルーチンへ値を渡せます。call文に続くラベルの後ろに引数として指定してください。
// sub01.mac
call mess "hanako";
call mess "tarou";
endmacro;
mess:
message "Hello ! " + $$1;
return;
カンマで区切って並べればいくつでも指定できます。 また、引数をサブルーチン側で参照するには、文字列型の引数の場合は$$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つの文字列型変数と2つの数値型変数を「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
戻り値として変数値をひとつ指定できます。サブルーチンの終わりを示す「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」という変数で参照できます。
次の例は、2つの数値型変数を「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;
サブルーチンからの戻り値は数値型変数だけでなく、文字列数値型でも得られます。 次の例は上の例とほぼ同じですが、戻り値を文字列型で返してします。
// 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;
戻り値そのものをマクロ文で利用できます。 次の例は、上の例とほぼ同じですが、戻り値を新しい変数に格納しないですぐに利用してます。
// 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;
秀丸マクロに限らず、ほとんどの言語では戻り値は1つです。複数の戻り値を扱いたい場合は、グローバル変数を用いることで対応します。
// 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はグローバル変数になっているので、 これらの変数は元のルーチンでも参照できます。 グローバル変数はサブルーチンで宣言されてもマクロ全域で有効です。
こうしてグローバル変数を用いると実質的に複数の戻り値を返せます。
: 仕事の内容からするとほぼ同じです。 ただ、サブルーチンは「マクロの実行される行を制御」しているので、関数のようにそれ自体が「値を持つ」ことができません。
ここでは、いくつかのサブルーチンの例を示します。
あくまでも、サブルーチンの作成例です。 各種変換は、変換モジュールを使用すれば、以下のサブルーチンを使用しなくても可能です。
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;
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;
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;
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;
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 文実行時の時刻を返します。
詳しくは、秀丸マクロヘルプの「内部的な値を表現するキーワード」-「日付と時刻関連」を参照してください。
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 文実行時の時刻を返します。
詳しくは、秀丸マクロヘルプの「内部的な値を表現するキーワード」-「日付と時刻関連」を参照してください。
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];
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;
備考 : 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;