Flash/Flex で作る俺様言語(kmyacc編)(1) - kmyacc の ActionScript 版を使ってみよう
- kmyacc の ActionScript 版を使ってみよう
- kmyacc で BASICっぽい言語を作ってみよう
本稿では、Flash CS3 や Flex2 で、手軽に自作プログラミング言語を作ってみる過程を紹介します。本格的な言語を作るのではなく、おもちゃ言語を作って、インタプリタの作り方や、パーサジェネレータの使い方を見ていきましょう。
kmyacc が ActionScript に対応した!
本稿のきっかけになったのは、2008年2月20日公開された kmyacc というパーサジェネレータの ActionScript 対応版です。パーサジェネレータというのは、文法の規則を与えることで、自動的にプログラミング言語の文法を解釈するパーサを生成するツールのことです。パーサジェネレータでは、C 言語のための Yacc が有名で、kmyacc は、Yacc を多言語対応させたものとなっています。
ActionScript で、俺様言語が作れるとなれば、これを使わない手はありません。本稿では、Flash CS3(または、Flex SDK)を利用して、簡単な俺様言語を作ってみようと思います。
kmyacc のダウンロード
kmyacc 本家では、ActionScript に対応していませんので、yukobaさんのブログから、対応版をダウンロードします。
- 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
いい気になって、変数定義をつけてみよう
次に、いい気になって、変数定義をつけてみようと思いますが、現状だと、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が画面に出力されるようになりました。
変数を追加する
では、いよいよ変数を追加してみます。これまでは、数値しか扱っていなかったので、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 = {};
以上、ここまでの変更を、Calc.asy として以下においておきますので、ダウンロードして遊んでみてください。
http://aoikujira.com/demo/hakkaku/rc/XlOFeMOxTxKk5jUx8JkdQg_Calc.asy
まとめ
以上、俺様言語と呼ぶには、おこがましいほど低機能ですが、サンプルを基に、簡単な変数計算を行うインタプリタもどきを作ってみました。今回は、構文をパースする段階で、直接プログラムを実行してしまいましたが、ここで構文木を作るようにすることで、制御構文を作ることができます。
このサイトについて
TrackBack URL :





