インストール

haXeのホームページはこちらです。
http://haxe.org/?lang=jp

日本語のページはこちら。
http://haxe.org/

haXeのページの面白いところは翻訳機能のついたwikiだってことです。
おかげで、日本人の興味のある人が興味のあるページだけ翻訳って事が出来て非常に理想的な形をしていると思います。
俺言語のホームページもこんな感じにできるといいなと。
っというのは置いておいて、2のダウンロードを選びます。
Windowsであれば、Windowsインストーラーをクリックしてダウンロードを開始します。
インストーラはとても小さいので通信環境によりますが、すぐにダウンロードが完了します。
後はダウンロードしたhx-inst.exeを実行して、ぽちぽち普通にインストールすれば完了です。
特に悩むことはないと思います。
次に、flash用にコンパイルして実行してみます。

Hello.hx

class Hello {
    static function main() {
        trace("Hello World !");
    }
}

Hello.hxml

-swf Hello.swf
-main Hello

以上の2つのファイルを作成します。
そして、Hello.hxmlをダブルクリックするか、コマンドラインからHello.hxmlを実行してコンパイルします。hxmlファイルはコンパイルを定義するファイルで、Hello.hxをコンパイルしてHello.swfファイルを作りますよーっていう定義になっています。
プログラムはstaticなmain関数が最初に起動されます。
traceはhaxeflashを作った場合、画面上に表示されます。
このコンパイルOcamlで作られているからか速いです。

さて、これでswfファイルは作成できるようになりました。
swfファイルはブラウザにドラッグ&ドロップしたりすればflashがインストールされているブラウザなら見ることが出来ると思います。
さて、それでは、お待ちかねの計算機を作っていきます。
というか、作ったものがあるのでこれを見てください。

class Calc {

	static function main() {
		var c = new Calc();
		trace(c.eval("1+2*3"));
	}

	function new(){}
	var src:String;
	var token:String;
	var token2:String;

	function eval(str:String):Int {
		src = str;
		lex();
		return exp();
	}

	function lex():String {
		var r : EReg = ~/^[\r\n\t ]*([0-9]+|[+*\-\/])/;
		token2 = token;
		if (r.match(src)) {
			token = r.matched(1);
			src = src.substr(r.matched(0).length);
			return token2;
		}
		token = "";
		return token2;
	}

	function exp():Int {
		var t = term();
		while (token == "+" || token == "-") {
			switch (lex()) {
			case "+": t = t + term();
			case "-": t = t - term();
			}
		}
		return t;
	}

	function term():Int {
		var t = fact();
		while(token=="*"||token=="/") {
			switch (lex()) {
			case "*": t = t * fact();
			case "/": t = cast(t / fact(),Int);
			}
		}
		return t;
	}

	function fact():Int {
		var t = lex();
		if(t == "(") {
			var t2 = exp();
			if(lex() != ")") throw ("error");
			return t2;
		} else {
			return Std.parseInt(t);
		}
	}
}

これといって、難しいことはしていませんが、
Haxeでの正規表現の使い方や、castの仕方、parseIntの使い方などが参考になるかと思います。
ここで、計算機の書き方についてかければいいんですけど、めんどくさいーっといってないで、ひとつ書いてみます。

えーと、まず、今回作るのは単なる計算機なのでCalcって言う名前のclassを作ります。

class Calc {

	static function main() {
		var c = new Calc();
		trace(c.eval("1+2*3"));
	}
}

そして、こんな感じでnewしてc.eval("1+2*3")を計算すると7が帰ってきて、traceで結果を画面に出すということをやれれば完成ってことを目指します。
ということで、まず、コンストラクタを作ります。ないと起こられるので。

	function new(){}

といっても、って書くだけです。コンストラクタがnewっていうのがほかと違うところですね。newするのはこのメソッドっていう意味ではいいけど、コンストラクタが特別な感じがしないのがデメリットかと。
次にevalのスタブだけ作って実行してみましょう。

class Calc {

	static function main() {
		var c = new Calc();
		trace(c.eval("1+2*3"));
	}
	function new(){}
	function eval(str:String):Int {
		return 7;
	}
}

そう、コンパイルするにはCalc.hxmlを作らないと。
Hello.hxmlをコピって書き換えましょう。

Calc.hxml

-swf9 Calc.swf
-main Calc
  • swf-version 9と書かないと、正規表現オブジェクトがないよエラーがでるので、つけます。

で、実行すると7と出力されます。
これで、一安心です。
次に字句解析器を作ります。字句解析っていうとなんだか難しそうですけどなんてことはありません。ただの文字列を字句と呼ばれるプログラムの最小単位にバラスだけです。字句は英語でトークン(token)といいます。字句解析器には英語でいろんな名前があってLexer, Scanner, tokenizerとかいいます。今回はlexという名前にします。理由は名前が短いからです。

	var src:String;
	var token:String;
	var token2:String;
	function lex():String {
		var r : EReg = ~/^[\r\n\t ]*([0-9]+|[+*\-\/])/;
		token2 = token;
		if (r.match(src)) {
			token = r.matched(1);
			src = src.substr(r.matched(0).length);
			return token2;
		}
		token = "";
		return token2;
	}

スクリプト言語ではたいてい正規表現が使えます。そして、正規表現を使って書くと高速で短い字句解析器が作れます。
HaxeではERegというオブジェクトが正規表現を扱うオブジェクトです。

		var r : EReg = ~/^[\r\n\t ]*([0-9]+|[+*\-\/])/;

のように~/と/の間に正規表現を書くことが出来ます。
Haxeでは型推論機能があるので実は

		var r = ~/^[\r\n\t ]*([0-9]+|[+*\-\/])/;

と書くこともできます。
ということで続きは後で書きます。ねむいのでちょっと寝て、会社行って帰ってきたので続き。

えーっと、なんだっけ?正規表現をなんか、書いてたんだったような気がする。
そう、字句解析のところで正規表現をつかってたんだった。
haxeではr.match(src)ってかんじで、正規表現オブジェクトのmatch関数で文字列をマッチングさせます。文字列オブジェクトのマッチング関数ではないというところが味噌というか、javascriptとかとは違います。
文字列と正規表現は別物って考えたのがhaxeなんでしょうねぇ。
で、マッチしたらカッコの中身は、r.matched(1)で1つ目の括弧、r.matched(0)で全体を現します。
srcの文字列をマッチした分だけ減らしてあとはマッチした値を保存して、保存してあったtoken2の値を返します。なんで、取得した値をすぐに返さないのかというと、先読みを1回するためです。先読み一回ってことでLL(1)文法が扱えるんだったような。あいまいだな。後で直す。こうかくと次のパーサ(構文解析器)を書くのに都合がいいんです。


ということで、次は構文解析器を作ります。

expが足し算、引き算、termが掛け算、割り算、factが数字あるいは、括弧ってなってます。で、こんな風に再帰的に呼び出してやると摩訶不思議。計算機のパーサが出来ちゃうんですね。ぜんぜん、説明になってないなぁ。

	function exp():Int {
		var t = term();
		while (token == "+" || token == "-") {
			switch (lex()) {
			case "+": t = t + term();
			case "-": t = t - term();
			}
		}
		return t;
	}

	function term():Int {
		var t = fact();
		while(token=="*"||token=="/") {
			switch (lex()) {
			case "*": t = t * fact();
			case "/": t = cast(t / fact(),Int);
			}
		}
		return t;
	}

	function fact():Int {
		var t = lex();
		if(t == "(") {
			var t2 = exp();
			if(lex() != ")") throw ("error");
			return t2;
		} else {
			return Std.parseInt(t);
		}
	}

まず、最初に1トークン先読みさせときます。
で、expを呼び出します。すると、termが呼ばれます。termよばれるとfactが呼ばれます。で、lex()を呼ばれるので、最初の1個目のトークンがtに入ります。最初のトークンが(でなければ、数字のはずなので、intであるはずと決め付けて、Std.parseInt(t)を呼び出して数値を返します。(ならその中身は式が書けるはずなのでexpを再起呼び出しします。で、式が終わったら、)がくるはずなので)でなかったらエラーを投げます。そうでなければ、)は読み捨てて値を返します。

termは次に掛け算か割り算の演算子"*"あるいは"/"があれば、繰り返してfactを呼び出します。そして、終わったら値を返します。
expはtermと同様に、termを呼び出して、"+"あるいは"-"があれば、繰り返してtermを呼び出して返します。


ってことで、これをぐりぐり繰り返すと計算結果が出ます。


さて、作った構文解析部分は値を計算して返してくれるのでeval関数を次のように書き換えれば、完成です。

	function eval(str:String):Int {
		src = str;
		lex();
		return exp();
	}

ってことで、とりあえず、haXeによる計算機が出来ました。