2009年9月21日月曜日

Treetop - RubyのPEG

これは面白いかも

Treetop is a language for describing languages. Combining the elegance of Ruby with cutting-edge parsing expression grammars, it helps you analyze syntax with revolutionarily ease.

Treetopは言語を記述するための言語だ。Rubyの簡潔さと最先端のParsing Expression Grammarを融合させたもので、構文解析を革新的に容易にする。

http://treetop.rubyforge.org/syntactic_recognition.html

2009年9月16日水曜日

Rubyでwithしてみた

VBで評価の高いWithをRubyで。
とっても簡単。
あらためてRubyすごいなぁ。

sample.rb
def with(instance, &block)
instance.instance_eval(&block)
end

class Hello
def say
puts 'hello world'
end
end

with(Hello.new) do
say
say
say
end

2009年9月3日木曜日

Flash Lite 1.1 で関数

言語で関数を定義することはできないけれど
通常は再生されないフレームにアクションを書いて
call(フレーム名) とする方法が使われる。

しかし、これは関数と言うよりはサブルーチンだ。
普通は再帰呼び出しすることはできない。

「それじゃ、引数や返り値の受け渡しのためにスタックを実装すれば、関数になるのでは?」

と思って作ってみた。

いろいろな実装方法が考えられるけれど、とりあえず次のようなルールとする。
  • 関数への引数は、関数を呼び出す側でスタックに積む。
  • 関数はスタックを参照して引数を得る。
  • 関数は返り値をスタックに積む。
  • 関数呼び出しの後に引数と返り値をスタックから取り除くのは、関数を呼び出す側の責任。

ルートムービークリップのフレーム1のアクション
sp = -1; // スタックポインタの初期化

// 引数をスタックに積む
++sp;
eval( "s" add sp ) = 12;

call ( "fact" ); // 関数呼び出し

// 答えをスタックから取り除く
r = eval ( "s" add sp );
--sp;

trace ( r ); // 確認

stop();

ルートムービークリップのフレーム2のアクション
// 階乗を計算する関数
n = eval ( "s" add sp );
if ( n == 0 ) {
eval ( "s" add sp ) = 1; // 引数をスタックから取り除き、答えをスタックに積む。つまりspは変化しない。
} else {
// 引数をスタックに積む
++sp;
eval ( "s" add sp ) = n - 1;
call ( "fact" );
r = eval ( "s" add sp ); // 関数の答えを取得する。
--sp; // スタックポインタを戻す
n = eval ( "s" add sp ); // n は再帰呼び出しで変化してるので復帰させる。
eval ( "s" add sp ) = r * n; // 引数をスタックから取り除き、答えをスタックに積む。つまりspは変化しない。
}

最後にルートムービークリップのフレーム2に名前"fact"をつけてプレビュー。

479001600

12!(12の階乗)を計算できた。
関数できた。
・・・かなり面倒だな。

Flash Lite 1.1でカレンダー計算:前の日を求める

prevday.as
// 前の日の日付を計算する
//
// @param a0 ... 西暦年 (例: 2009 )
// @param a1 ... 月 (1?12)
// @param a2 ... 日 (1?31)
//
// @return a0 ... 西暦年 (例: 2009 )
// @return a1 ... 月 (1?12)
// @return a2 ... 日 (1?31)
//
--a2;
if (0 >= a2) {
switch (a1) {
case 1:
a0 -= 1; a1 = 12; a2 = 31;
break;
case 2:
case 4:
case 6:
case 8:
case 9:
case 11:
--a1; a2 = 31;
break;
case 3:
a1 = 2;
if ((a0 % 4 == 0 && a0 % 100 != 0) || (a0 % 400 == 0)) { // 閏年
a2 = 29;
} else {
a2 = 28;
}
break;
case 5:
case 7:
case 10:
case 12:
--a1; a2 = 30;
break;
}
}

Flash Lite 1.1でカレンダー計算:次の日を求める

nextday.as
// 次の日の日付を計算する
//
// @param a0 ... 西暦年 (例: 2009 )
// @param a1 ... 月 (1?12)
// @param a2 ... 日 (1?31)
//
// @return a0 ... 西暦年 (例: 2009 )
// @return a1 ... 月 (1?12)
// @return a2 ... 日 (1?31)
//
++a2;
switch (a1) {
case 1:
case 3:
case 5:
case 7:
case 8:
case 10:
if (31 < a2) { ++a1; a2 = 1; }
break;
case 2:
if ((a0 % 4 == 0 && a0 % 100 != 0) || (a0 % 400 == 0)) { // 閏年
if (29 < a2) { ++a1; a2 = 1; }
} else {
if (28 < a2) { ++a1; a2 = 1; }
}
break;
case 4:
case 6:
case 9:
case 11:
if (30 < a2) { ++a1; a2 = 1; }
break;
case 12:
if (31 < a2) { ++a0; a1 = 1; a2 = 1; }
break;
}

Flash Lite 1.1でカレンダー計算:曜日を求める

weekday.as
// 日付から曜日を求める
//
// @param a0 ... 西暦年 (例: 2009)
// @param a1 ... 月 (1?12)
// @param a2 ... 日 (1?31)
//
// @return r0 ... 0?6 {0:日曜, 1:月曜, ... , 6:土曜}
//
if (2 >= a1) { // 1月と2月は前年の13月と14月と見なす
--a0;
a1 += 12;
}
// Zellerの公式の変形式
r0 = (a0+int(a0/4)-int(a0/100)+int(a0/400)+int((13*a1+8)/5)+a2)%7;
if (12 < a1) { // 13月と14月を元に戻す
++a0;
a1 -= 12;
}

SWF4でevalする時のTips

Flash CS4で作成したSWF4のバイトコードを読んでみた。
SWF4はFlash Lite 1.1で作成したプロジェクトの出力フォーマット。

ActionScriptで
eval(varInstName).varname = "value";
このコードは、次のようなバイトコードになる。
1: ActionPush "varInstName"
2: ActionGetVariable
3: ActionPush ":"
4: ActionStringAdd
5: ActionPush "varname"
6: ActionStringAdd
7: ActionPush "value"
8: ActionSetVariable
一方、
eval(varInstName add ":varname") = "value";
同じ効果を持つこのコードは、
1: ActionPush "varInstName"
2: ActionGetVariable
3: ActionPush ":varname"
4: ActionStringAdd
5: ActionPush "value"
6: ActionSetVariable
にコンパイルされるので、":"と"varname"をActionStringAddするコードを1つにできて、swfのバイトコードを6バイト減量できる。
命令数も減るので運がよければ速くなるかも。

ちなみに、
eval("/InstName:varname") = "value";
_root.InstName.varname = "value";
は、同じバイトコードを出力し、
1: ActionPush "/InstName:varname"
2: ActionPush "value"
3: ActionSetVariable
だった。

2009年9月2日水曜日

Zellerの公式

まぁ普通はZellerの公式使ってすっきり書くのでしょうねぇ・・・

weekday.rb
#!/usr/bin/env ruby

# 曜日を求める
# y ... 西暦年
# m ... 月
# d ... 日
def weekday(y, m, d)
if 2 >= m
y -= 1
m += 12
end
(y + (y / 4) - (y / 100) + (y / 400) + (13 * m + 8) / 5 + d) % 7
end

require 'date'

def check
d = Date.new(2000, 1, 1)
50000.times do
unless d.wday == weekday(d.year, d.month, d.day)
puts "#{d} ng"
break
end
d = d.next
end
end

check

2009年9月1日火曜日

曜日を計算する関数を作ってみた

カレンダーライブラリが使えない環境で任意の日付の曜日を求める必要があったので、いろいろごちゃごちゃやっている間に何となくできた関数。

weekday.rb
#!/usr/bin/env ruby

# 曜日を求める
# y ... 西暦年
# m ... 月
# d ... 日
def weekday(y, m, d)
ms = [ 0, 3, 3, 6, 1, 4, 6, 2, 5, 0, 3, 5 ]
x = y
if 2 >= m
y -= 1
end
(x + (y / 4) - (y / 100) + (y / 400) + ms[m-1] + (d-1)) % 7
end

require 'date'

def check
d = Date.new(2000, 1, 1)
50000.times do
unless d.wday == weekday(d.year, d.month, d.day)
puts "#{d} ng"
break
end
d = d.next
end
end

check