2017年8月12日土曜日

秀丸マクロを生成する秀スクリプトという言語処理系を作った

エグゼクティブサマリ

秀スクリプトという小さな言語処理系を開発した。秀スクリプトは、TypeScriptを大幅に縮小した文法を持ち、コンパイラによって秀丸マクロに変換され、秀丸上で実行される。秀スクリプトコンパイラは秀スクリプト自身により記述される。
 秀スクリプトの主な特徴は下記のとおり。
  • TypeScriptに似た文法を持ち、コンパイラも秀スクリプトで記述されている
  • 秀丸マクロを生成し、秀丸上で実行される
  • TypeScript同様、変数に型がある
  • 数値(整数)型と文字列型、これらの配列をサポート
  • if、while、do … whileの制御構造
  • 関数のサポート

はじめに

Windows上で使用するエディタとして長年秀丸を愛用しています。最近はVisual Studio Codeなども人気で、僕もインストールして使ってみたりはするのですが、長年手に馴染んだツールというのは、中々乗り換えが難しいものですね…ということで、普段は、もっぱら秀丸を使います。
秀丸には、秀丸マクロという簡単なマクロ言語があり、機能を追加することができますが、この秀丸マクロがなかなか厄介な代物…もとい、なんと言うか独特の仕様なのでとっつきにくい感じがします。以下は、秀丸マクロで書いたフィボナッチ数値のマクロです。ほら、文章を書いているとたまにフィボナッチ数列を引用したい時、あるでしょ…えっ、ないですか?
#n=1;
while (#n <= 10) {
  call fib #n;
  insert str(##return) + "\n";
  #n = #n + 1;
}
endmacro

fib:
  if (##1 <= 2) {
    return 1;
  }
  call fib ##1 - 2;
  ##temp = ##return;
  call fib ##1 - 1;
  return ##temp + ##return;
ご覧のように、中々独特の構文です。特徴を書いておくと下記のようになります。
  • 変数には型があり、整数型の変数は #foo、文字列型は $bar 等と書く
  • サブルーチンや関数が使える。引数は $$1 や ##1 等と記述する
  • サブルーチンや関数の呼び出しは、call文を用いる
  • ローカル変数が使える。ローカル変数は $$foo、##bar 等と記述する
  • 関数の戻り値は ##return という変数にセットされる
  • 制御構文として、ifやwhileが使える
というわけで、ちょっととっつきにくいですよね。仮に、秀丸マクロがJavaScript風の文法だったら、以下のようになるはずです。
function fib(n) {
  if (n <= 2)
    return 1;
  return fib(n - 2) + fib(n - 1);
}

var x = 1;
while (x <= 10) {
  insert(str(fib(x)) + "\n");
  x = x + 1;
}
うん、この方が分かりやすい…では、いっそ、JavaScript風の言語から秀丸マクロに変換するコンパイラ(トランスレータ、トランスパイラ)を作ってしまえと思い立ちました。

目標を決める

最初、秀丸マクロ使って以下のような簡単なコンパイラを書こうかと思っていたのですが、
  • 秀丸マクロを生成するトランスパイラ型の処理系
  • コンパイラ自体は秀丸マクロで記述
試作しているうちに、どうも秀丸マクロでは作るのが辛いことと、昔Pascalコンパイラを作ったときのように「コンパイラ自体を記述できる」というのが格好いいなと思い始め、以下のように目標を決めました。この言語を「秀スクリプト」と呼ぶことにします。
  • 秀丸マクロを生成するトランスパイラ型の処理系
  • コンパイラ自体を記述できる

ブートストラップ方針を決める

秀スクリプトのコンパイラが、秀スクリプト自身で記述してあるというと、最初の秀スクリプトコンパイラはどうやって作るのだろうという疑問が生まれますよね。この問題のことをブートストラップ問題というそうで、Wikipediaにも記載されています。
秀スクリプトの場合は、以下の戦略が考えられます。
  1. 秀スクリプトを既存言語のサブセットとして定義し、その既存言語のコンパイラでコンパイルする
  2. 最初は秀丸マクロでコンパイラを書き、次に秀スクリプトでコンパイラを書き直す
2は二度手間で面倒なので、できれば 1で行きたいですね。そうなると、既存言語で適当なものがあるかが問題になります。この「既存言語」のことをこれ以降は「親言語」と呼ぶことにします。

親言語を決める

親言語の選定は重要です。秀スクリプトは、その親言語のサブセットになるので、親言語の選定すなわち秀スクリプトの言語仕様を決めることに近いからです。
冒頭でJavaScriptで書けたらいいよね、みたいなことを書きましたが、実際にはJavaScriptから秀丸マクロへのコンパイル(変換)は困難です。なぜなら、秀丸マクロには「変数に型がある」からです。すなわち、冒頭で述べたように整数型の変数は #で始まり、文字列型の変数は $で始まるという仕様なので、どちらの変数を生成するかを決めるには、変数に型宣言をするか、型推論のようなややこしいことをしなければなりません。型推論は手におえないので、型宣言があるスクリプト言語を探すことにしました。
色々な言語を見たのですが、結局TypeScriptに落ち着きました。型のあるJavaScriptという感じで、イメージ通りです。Visual Studio 2017上でTypeScriptでWindowsコンソールアプリを開発する場合は Node.jsが選択される、ということで、生まれて初めてTypeScriptで、生まれて初めてNode.jsで開発することになりました。

秀スクリプトの言語仕様

秀スクリプトの言語仕様については、こちらをご覧ください。
親言語としてTypeScriptの文法を借りているとは言え、機能的にはBASICくらいのものですので、過度な期待はしないでくださいねw
以下は、冒頭で上げたフィボナッチ数列の記述例。概ね、当初の期待通りには書ける、というところですね。

使ってみる

フィボナッチ数列を埋め込むスクリプトを書いてみましょう。数値を秀丸上で選択してスクリプトを実行すると、その数値の個数だけフィボナッチ数列を生成するものです。
function fib(n : number) : number {
  if (n <= 2)
    return 1;
  return fib(n - 2) + fib(n - 1);
}

var x = val(gettext(seltopx(), seltopy(), selendx(), selendy(), 1)); 

var n = 1;
while (n <= x) {
  insertln(fib(n));
  n = n + 1;
}
コンパイル結果は下記となります。見やすいように空白や改行を補って整形しています。
goto _end_fib
fib:
    if (##1 <= 2) {
        return 1;
    }
    call fib  ##1 - 2;
    ##_0 = ##return;
    call fib  ##1 - 1;
    ##_1 = ##return;
    return ##_0 + ##_1;
    return 0;
_end_fib:

#x = val(gettext(seltopx, seltopy, selendx, selendy, 1));
#n = 1;
goto _LL1
_LL0:
    call fib  #n;
    #_0 = ##return;
    insert str(#_0) + "\n";
    #n = #n + 1;
_LL1:
if (#n <= #x) goto _LL0
_LL2:

これを動かしてみましょう。以下は、秀スクリプトのコンパイルから実行の映像です。



…ということで、秀丸を使っていて、文章中にフィボナッチ数列を埋め込む様子をご覧いただきました。

ダウンロード

githubのリポジトリから、Clone or Download▼ ボタンの Download ZIP によりZIPファイルをダウンロードしてください。あるいはリリースページから適当なzipファイルをダウンロードしてください。

hidescript/hidescript フォルダ内の下記のファイルがコンパイラ本体です。

  • hidescript.ts  TypeScript/秀スクリプトで書かれた秀スクリプトコンパイラソース
  • hidescript.js   TypeScriptにより変換された JavaScriptソース
  • hidescript.mac node.js/秀スクリプトによりコンパイルされた秀丸マクロ


使い方

秀スクリプトでソースを書いた後、秀丸マクロに変換するには以下の方法があります。

(1) 秀スクリプトコンパイラをTypeScriptとして解釈してJavaScriptにコンパイルした hidescript.js を Node.js上で動かす。
C:>node hidescript.js foo.hs
(2) 秀スクリプト記述の秀スクリプトコンパイラを自分自身でコンパイルした hidescript.macを用いて、秀丸上でコンパイルする
hidescript.macを秀丸にマクロ登録して、ショートカット等でコンパイラを起動します。実際には、自分自身をコンパイルする際に備えて、hidescript.macをhs.mac等にリネームして登録することをお勧めします。詳しくは、上記の映像をご覧ください。

まとめ

秀スクリプトという秀丸マクロを生成するスクリプト言語を開発しました。秀スクリプトは秀丸マクロの開発を便利にします。秀丸マクロはコンパイル時のチェックが非常に緩く、実行が始まってからさまざまなエラーが出たり、未定義の変数を参照してもエラーにならなかったりしますが、秀スクリプトは変数などの型などを厳密にチェックするからです。
まだ、組み込み関数などの定義が最低限のものしかありませんが、自分で追加することもできるので遊んでみて下さい。

謝辞

秀スクリプトの開発にあたり、秀丸マクロのサブルーチンのネスト回数が20回までという制限が厳しく、サポートフォーラムにて制限の緩和を要望したところ、200回までに緩和いただきました。この仕様緩和で、秀スクリプトを秀丸上でコンパイルできるようになりました。ご担当いただいた「秀丸担当」様、サイトー企画様にあつくお礼申し上げます。ありがとうございました。

0 件のコメント:

コメントを投稿