トップ » 技術記事 » Flash/Flex で作る俺様言語(kmyacc編)(1) - kmyacc の ActionScript 版を使ってみよう

Flash/Flex で作る俺様言語(kmyacc編)(1) - kmyacc の ActionScript 版を使ってみよう

タグ: ActionScript Flash Flex

本稿では、Flash CS3 や Flex2 で、手軽に自作プログラミング言語を作ってみる過程を紹介します。本格的な言語を作るのではなく、おもちゃ言語を作って、インタプリタの作り方や、パーサジェネレータの使い方を見ていきましょう。

kmyacc が ActionScript に対応した!

本稿のきっかけになったのは、2008年2月20日公開された kmyacc というパーサジェネレータの ActionScript 対応版です。パーサジェネレータというのは、文法の規則を与えることで、自動的にプログラミング言語の文法を解釈するパーサを生成するツールのことです。パーサジェネレータでは、C 言語のための Yacc が有名で、kmyacc は、Yacc を多言語対応させたものとなっています。

ActionScript で、俺様言語が作れるとなれば、これを使わない手はありません。本稿では、Flash CS3(または、Flex SDK)を利用して、簡単な俺様言語を作ってみようと思います。

kmyacc のダウンロード

kmyacc 本家では、ActionScript に対応していませんので、yukobaさんのブログから、対応版をダウンロードします。

kmyacc のコンパイル(または、バイナリの取得)

gcc があれば、make 一発でコンパイルが可能です。上記のアーカイブをダウンロードしたら、解凍して、コマンドラインから以下のようにコマンドを入力します。

make

しかし、もし、Windows を使っている方で、C言語のコンパイル環境を整えるのがダルいという方のために、Windows版を用意しました。上記をコンパイルしただけのものですので、勝手に使ってください。

※yukobaさんのサイトにあるサンプルやソースは、変更される可能性があるので、もし、最新版を使ってみて内容が変わってしまっていたら、上記の zip ファイルを利用して作業してください。

サンプルを変換してみる

ここでは、ActionScript3 のサンプルを、変換してみたいと思います。ソースファイル版から make した方は、sample フォルダにある、Calc.asy を確認してください。そして、バイナリ版を取得した方は、ルートフォルダにある Calc.asy を確認してください。

Calc.asy は、簡単な電卓のサンプルの文法規則を定義したものです。コマンドラインから以下のように入力することで、ActionScript3 のソースコードを出力させることができます。

> kmyacc -d Calc.asy

もし、問題がなければ、Calc.as というファイルが生成されているはずです。これを Flex SDK でコンパイルするには、コマンドから以下のように入力します。

> mxmlc Calc.as

ここでは、分かりやすく、Flash CS3 でこのファイルを実行してみたいと思います。Flash でこのファイルをコンパイルするには、以下のようにします。

  • (1) Flash CS3 で新規ドキュメント(AS3.0)を作成します
  • (2) CalcTest.fla という名前で保存します。
  • (3) Calc.as を CalcTest.fla と同じフォルダにコピーします。
  • (4) ステージをクリックしてから、プロパティインスペクタを開きます。
  • (5) ドキュメントクラスに、Calc と入力します。
  • (7) 制御メニューから、ムービープレビューを実行します。
実行してみたところ
実行してみたところ

この状態だと、うまく動いていることは分かるのですが、結果がよく分かりません。デバッグオプションをオフにしてみましょう。以下のようにコンパイルしなおします。

> kmyacc Calc.asy

そして、Calc.as の冒頭にある以下の行を削除します。

CalcParser.yyDumpParseTree = true;

再度、実行してみましょう。

再度実行してみたところ
再度実行してみたところ

Calc.asy の計算は次のようになっています。

cp.inputString = "2 + 3 * 4\n" + "5 * 6 + 7 * 8\n";

14と86で合っているようです。

拡張子と関連ファイルのまとめ

ここで、ちょっと、kmyacc のファイルと、Flash ファイルをまとめてみます。

拡張子 説明
Calc.asy ActionScript版 kmyaccの文法定義ファイル
Calc.as ActionScriptのファイル (.asy から生成される)
CalcTest.fla Flashのドキュメントファイル

Flex SDK なら、Calc.as をそのまま実行できますが、Flash CS3 の場合は、ドキュメントクラスに Calc を関連づける必要があります。

サンプルを改造してみよう

では、サンプルを改造することからはじめてみましょう。

文法定義ファイルの書き方

その前に、文法定義ファイルの仕組みを見てみましょう。

Calc.asy から抜粋~

%{
// --- メインクラスの定義
package {
    import flash.display.*;
	public class Calc extends MovieClip {
	  public function Calc() {
}
%}
// --- 文法の定義(1) -- トークンの定義
%token NUMBER

%left '+' '-'
%left '*' '/'

%%
// --- 文法の定義(2) --- 構文規則
start:	lines;
lines: /* empty */
	| lines line { yylrec++; }
%%
// --- 字句解析のコード
/* Lexical analyzer */

これを書式化すると次のようになります。

%{
// ActionScript ヘッダ
%}
// トークン定義
%%
// 構文定義
%%
// ActionScript フッタ

ActionScript のヘッダとフッタは、普通のコードです。問題となるのは、トークンの定義や構文の定義です。Calcクラスで利用しているトークンの種類は、NUMBERだけです。記述の方法は、以下の通りです。

%token NUMBER
%left '+' '-'
%left '*' '/'

2~3行目にある、%left というのは、演算子を左結合にするという意味です。また、演算子の優先順位も決定しています。

そして、構文定義は次のようになっています。

start: lines;
lines: /* empty */
     | lines line { yylrec++; }
     ;
line : expr '\n' { trace("result = " + $1); }
     | '\n' { trace("(empty line ignored)"); }
     | error '\n'
     ;
expr : expr '+' expr { $$ = $1 + $3; }
     | expr '-' expr { $$ = $1 - $3; }
     | expr '*' expr { $$ = $1 * $3; }
     | expr '/' expr { $$ = $1 / $3; }
     | '(' expr ')'  { $$ = $2; }
     | NUMBER { $$ = $1; }
     ;

ちょっと複雑そうに見えますが、書式は基本的に以下のようになっています。

規則名 : トークン トークン .. { 処理するコード } ;

もし、トークンの並び方によって、別の構文規則を適用したい場合は、以下のようになります。

規則名 : トークン トークン .. { 処理するコード }
       | トークン トークン .. { 処理するコード }
       | トークン トークン .. { 処理するコード }
       | ..
       ;

それから、もう一つ重要なことですが、上記の処理するコードの部分では、トークンを、$1 $2 $3 .. のような特殊記号に置き換えることができます。そして、実行結果を、$$ に返すように指定することができます。例えば、足し算を行っている部分を見てみるとよく分かると思います。

expr : expr '+' expr { $$ = $1 + $3; }
割り算の余り「%」記号が使えるように改造してみよう

ここまでの説明を読んでいれば、割り算の余りを追加するのも簡単でしょうか。まず、構文規則に、以下の一文を追加します。

expr : expr '%' expr { $$ = $1 % $3; }

そして、トークンの定義を、以下のように書き換えましょう。

%left '*' '/'
   ↓
%left '*' '/' '%'

それから、冒頭にあるプログラムで入力する式もちょっと変えてみます。

cp.inputString = "5 % 3\n";

コンパイルして確認してみます。

> kmyacc Calc.asy
http://aoikujira.com/demo/hakkaku/rc/RadvDerPQQSG1t0vm669GA_2.png

いい気になって、変数定義をつけてみよう

次に、いい気になって、変数定義をつけてみようと思いますが、現状だと、trace 文で出力パネルに出すだけだと面白くないので、先に、値を画面に出力するコマンド「>」をつけてみようと思います。

画面に出力するコマンド「>」を追加する

この改良は、主に、Calc.asy の ActionScript のヘッダ部分に、outout()というstaticな関数を追加します。クラス Calc の定義を次のようにしました。

%{
package {
import flash.display.*;
import flash.text.*;
	public class Calc extends MovieClip {
	  public static var _i:Calc; // main instance --- (*)
	  public function Calc() {
	    _i = this;               // ----------------- (*)
	    var cp:CalcParser = new CalcParser();
	    cp.inputString = "> 1 + 2 * 3\n";
	    cp.yyparse();
	  }
	  // output command ------------------ この関数を追加 (*)
	  // output command
	  private static var output_y:Number = 0;
	  public static function output(v:int):void {
	    var t:TextField = new TextField();
	    t.autoSize = TextFieldAutoSize.LEFT;
		t.x = 10;
	    t.y = output_y;
	    t.text = "> " + v;
	    output_y += t.height;
		_i.addChild(t);
	  }
	}
}
%}

そして、文法定義の line を次のようにコマンド出力を書き加えました。

line	: expr '\n' { trace("result = " + $1); }
	| '>' expr '\n' { Calc.output( $2 ); } /*  --- 追加 */
	| '\n' { trace("(empty line ignored)"); }
	| error '\n'
	;

これを、コンパイルすると無事に、1+2*3の結果7が画面に出力されるようになりました。

http://aoikujira.com/demo/hakkaku/rc/cG0Mdv0dR3OC3ZWqeSTAaA_7.png
変数を追加する

では、いよいよ変数を追加してみます。これまでは、数値しか扱っていなかったので、NUMBER 型のトークンか、’+’ や ‘-’ などの演算子のトークンの2種類だけでした。そこで、まずは、WORD 型のトークンを認識するように、トークンに WORD を付け加えます。

%token NUMBER
%token WORD /* ←追加 */

ただ、これだけで、WORD 型が使えるわけではありません。ソースコードを読み込んで、WORD型と判定する、字句解析のコードをつけなくてはならないのです。

ここまで、説明を省略してきましたが、基本的にパーサジェネレータというのは、構文文法からプログラムを生成するだけのものなので、インタプリタ作成において基本となる字句解析は専門外です。

[ここで作るインタプリタの実行手順]
字句解析 → 構文解析 → 実行

そこで、字句解析を行っているのが、ActionScript のフッタの部分で定義されていた関数「yylex()」です。この「yylex()」で、WORD型を返す部分を作ってあげます。

private function isAlpha(s:String):Boolean {
  s = s.charAt(0);
  return ("a" <= s && s <= "z" || s == "_" || "A" <= s && s <= "Z");
}
private function yylex():int {
  yylval = null;
  for (;;) {
    var c:String = getch();
    if (c == null) {
      return 0;
    } else if (c == ' ' || c == '\t') {
      // skip
    } else if (isDigit(c)) {
      var n:int = 0;
      var buf:Array = [];
      while (c != null && isDigit(c)) {
        buf.push(c)
        c = getch();
      }
      ungetch(c);
      yylval = parseInt(buf.join());
      return NUMBER;
    } else if (isAlpha(c)) {
      var id:String = "";
      while (c != null && isAlpha(c)) {
        id += c;
        c = getch();
      }
      ungetch(c);
      yylval = id;
      return WORD;
    } else {
      return c.charCodeAt(0);
    }
  }
  throw "Doesn't come here";
}

これで、WORDを使う準備が整いました。では、変数の代入を行うコードと取得を行うコードを追加してみます。まずは、代入からです。

line	: expr '\n' { trace("result = " + $1); }
	| '>' expr '\n' { Calc.output( $2 ); }
	| '\n' { trace("(empty line ignored)"); }
	| WORD '=' expr { calc_vars[$1] = $3; } /* ---- 追加  */
	| error '\n'
	;

次に参照です。

expr	: expr '+' expr { $$ = $1 + $3; }
	| expr '-' expr { $$ = $1 - $3; }
	| expr '*' expr { $$ = $1 * $3; }
	| expr '/' expr { $$ = $1 / $3; }
	| expr '%' expr { $$ = ($1) % ($3); }
	| '(' expr ')' { $$ = $2; }
	| NUMBER { $$ = $1; }
	| WORD   { $$ = calc_vars[$1]; } /* ---- 追加 */
	;

それから、変数を統括する、プロパティをパーサクラスに追加しておきます。ActionScriptのフッタ部分に以下の一行を追加します。

private var calc_vars:Object = {};
http://aoikujira.com/demo/hakkaku/rc/1zykmGqPRu6WfqYdP2fGJA_636.png

以上、ここまでの変更を、Calc.asy として以下においておきますので、ダウンロードして遊んでみてください。

http://aoikujira.com/demo/hakkaku/rc/XlOFeMOxTxKk5jUx8JkdQg_Calc.asy

まとめ

以上、俺様言語と呼ぶには、おこがましいほど低機能ですが、サンプルを基に、簡単な変数計算を行うインタプリタもどきを作ってみました。今回は、構文をパースする段階で、直接プログラムを実行してしまいましたが、ここで構文木を作るようにすることで、制御構文を作ることができます。

Series Navigationkmyacc で BASICっぽい言語を作ってみよう»

執筆者紹介

クジラ飛行机

クジラ飛行机

くじらはんど(http://kujirahand/)にて、日本語プログラミング言語「なでしこ」(IPA未踏ユース採択)、テキスト音楽「サクラ」(OSPオンラインソフト大賞入賞)など多くのオンラインソフトを開発。著書に「Flexプロフェッショナルガイド」「なでしこ公式バイブル」、「一週間でマスターするActionScript3.0」など。

TrackBack URL :