SICP 読み (398) 5.5 翻訳系

昨日は最新のソースツリーを職場方面に送付するのを忘れてて作業できず。当分何もしていないのでリハビリが必要な世界になってるなぁ。
とりあえず (397) なエントリの最後で

これは number.c をパクって試験だけ書くか。

と書いてますが、コピーしてきて既存の試験が動いているだけ、な状態で中断している模様。warning が出まくっている。試験も書いてない。
現状は確認できたので作業すっか。地味にフラグとり。この際だから warning なナニも全部直してしまえ。

機械的に warning な対処。使ってない関数定義とか全部削除したいのですが、そこまで調べて丁寧にヤるリキが無い。次は number な試験の作成ッスか。とりあえず gauche-0.1 の number.c で定義されている関数を列挙してみた

$ grep '^ScmObj' number.c
ScmObj Scm_MakeFlonum(double d)
ScmObj Scm_MakeFlonumToNumber(double d, int exact)
ScmObj Scm_MakeComplex(double r, double i)
ScmObj Scm_Magnitude(ScmObj z)
ScmObj Scm_Angle(ScmObj z)
ScmObj Scm_MakeInteger(long i)
ScmObj Scm_NumberP(ScmObj obj)
ScmObj Scm_IntegerP(ScmObj obj)
ScmObj Scm_Abs(ScmObj obj)
ScmObj Scm_Negate(ScmObj obj)
ScmObj Scm_Reciprocal(ScmObj obj)
ScmObj Scm_ExactToInexact(ScmObj obj)
ScmObj Scm_InexactToExact(ScmObj obj)
ScmObj Scm_PromoteToBignum(ScmObj obj)
ScmObj Scm_PromoteToFlonum(ScmObj obj)
ScmObj Scm_PromoteToComplex(ScmObj obj)
ScmObj Scm_Add(ScmObj args)
ScmObj Scm_Subtract(ScmObj arg0, ScmObj arg1, ScmObj args)
ScmObj Scm_Multiply(ScmObj args)
ScmObj Scm_Divide(ScmObj arg0, ScmObj arg1, ScmObj args)
ScmObj Scm_Quotient(ScmObj x, ScmObj y)
ScmObj Scm_Modulo(ScmObj x, ScmObj y, int reminder)
ScmObj Scm_NumEq(ScmObj arg0, ScmObj arg1, ScmObj args)
ScmObj FN(ScmObj arg0, ScmObj arg1, ScmObj args)                        \
ScmObj Scm_Max(ScmObj arg0, ScmObj args)
ScmObj Scm_Min(ScmObj arg0, ScmObj args)
ScmObj Scm_Round(ScmObj num, int mode)
ScmObj sfn(ScmObj z)                                                     \
ScmObj Scm_Atan2(ScmObj y, ScmObj x)
ScmObj Scm_Expt(ScmObj x, ScmObj y)
ScmObj Scm_Sqrt(ScmObj z)
ScmObj Scm_NumberToString(ScmObj obj, int radix)
ScmObj Scm_StringToNumber(ScmString *str, int radix)
$

よく見るとマクロもある FN とか sfn とかはマクロの中身。って MakeBignum* な定義が無いぞ、というコトで gauche-0.1 なソースツリー見てみたら bignum.c ってのがある。先にこれ持ってきて試験かなぁ。なんか作る前段階でへとへと。
でもここのハードル超えたら C で scheme の基本データをリストでごりごり、な事ができるようになってくれるはずなんですが駄目なのかなぁ。なんか無駄な徒労に終わる確立が非常に高い気がしてきていたりします。しかもパクッてきたソレ達は微妙にぼろぼろだし。

bignum.c

コピーして機械的なフラグ取り作業。その後、ようやく bignum.c 方面から試験の作成に着手。でも今回パクッた number.c と bignum.c については全部試験しないとマズいかなぁ。どうしたものやら。一瞬試験もスルーという考えが脳の片隅をよぎりましたが、試験書かないと中身見ないし、てーかむしろ中身を見るのが (以下略
ゴタクは置いといて試験を書こう。まず bignum な構造体の定義が以下

struct ScmBignumRec {
    SCM_HEADER;
    short sign;
    u_short size;
    u_long values[1];           /* variable length */
};

コメントが怪しげ。ちなみに書こうとしているのは Scm_MakeBignumFromSI() 関数で定義は以下

ScmObj Scm_MakeBignumFromSI(long val)
{   
    ScmBignum *b;
    if (val == LONG_MIN) {
        b = make_bignum(2);
        b->sign = -1;
        b->values[0] = 0;
        b->values[1] = 1;
    } else if (val < 0) {
        b = make_bignum(1);
        b->sign = -1;
        b->values[0] = -val;
    } else {
        b = make_bignum(1);
        b->sign = 1;
        b->values[0] = val;
    }
    return SCM_OBJ(b);
}

あるいは make_bignum() 関数がこうなっている

static ScmBignum *make_bignum(int size)
{   
    ScmBignum *b = SCM_NEW_ATOMIC2(ScmBignum*,
                                   sizeof(ScmBignum)+(size-1)*sizeof(long));
    SCM_SET_CLASS(b, SCM_CLASS_INTEGER);
    b->size = size;
    return b;
}

ええと、SCM_NEW_ATOMIC2 マクロは

#define SCM_NEW_ATOMIC2(type, size) ((type)(SCM_MALLOC_ATOMIC(size)))

となっていて SCM_MALLOC_ATOMIC マクロは GC_MALLOC_ATOMIC です。google 先生に伺ってみた所では内部にポインタを含まない構造体の領域を確保する時に使うとの事。そこから派生して領域が確保されていない、という目印が付くんでしょうね。こうした目印があれば後片付けの効率は上がるな。
で、領域確保時にも微妙な操作をしている。size という引数によって確保される領域が異なるように読めます (make_bignum())。でも何故に val が LONG_MIN の時だけこうした処理を行なっているのか、がイマイチ見えてない。とりあえずここだけで見える情報としては

  • hdr は scheme オブジェクトのヘッダ
  • sign は符号
  • size は values のサイズ
  • value は値 (unsigned long)

となっていると読める。ええと処理的には

  • val が LONG_MIN
  • val が負
  • val が正

で処理が振り分けられている。取り出す時の問題なんだろうからそのまま試験を書きましょう。以下。

void test_scheme_Scm_MakeBignumFromSI(void)
{
    ScmObj obj;
    long v;

    v = LONG_MIN;
    obj = Scm_MakeBignumFromSI(v);
    CU_ASSERT_TRUE(SCM_BIGNUMP(obj));
    CU_ASSERT_EQUAL(2, SCM_BIGNUM_SIZE(obj));
    CU_ASSERT_EQUAL(-1, SCM_BIGNUM_SIGN(obj));
    CU_ASSERT_EQUAL(0, ((ScmBignum*)obj)->values[0]);
    CU_ASSERT_EQUAL(1, ((ScmBignum*)obj)->values[1]);

    v = LONG_MAX;
    obj = Scm_MakeBignumFromSI(v);
    CU_ASSERT_TRUE(SCM_BIGNUMP(obj));
    CU_ASSERT_EQUAL(1, SCM_BIGNUM_SIZE(obj));
    CU_ASSERT_EQUAL(1, SCM_BIGNUM_SIGN(obj));
    CU_ASSERT_EQUAL(LONG_MAX, ((ScmBignum*)obj)->values[0]);

    v = LONG_MIN + 1;
    obj = Scm_MakeBignumFromSI(v);
    CU_ASSERT_TRUE(SCM_BIGNUMP(obj));
    CU_ASSERT_EQUAL(1, SCM_BIGNUM_SIZE(obj));
    CU_ASSERT_EQUAL(-1, SCM_BIGNUM_SIGN(obj));
    CU_ASSERT_EQUAL(-(LONG_MIN + 1), ((ScmBignum*)obj)->values[0]);
}

何箇所か微妙なボケをカマシています。

  • LONG_MIN - 1 ってヤッちゃった
  • CU_ASSERT_EQUAL(LONG_MIN + 1, ((ScmBignum*)obj)->values[0]); ってヤッちゃった

しかし ScmBignum の values は u_long でとってるんだから先頭 1bit 分がナニな気がするのはしなくて良い心配なのかなぁ。

Scm_MakeBignumFromUI() の試験。中身が以下

ScmObj Scm_MakeBignumFromUI(u_long val)
{   
    ScmBignum *b = make_bignum(1);
    b->sign = 1;
    b->values[0] = val;
    return SCM_OBJ(b);
}

これを見ると確かに負の数は 1 bit ナニかもしれませんが、同じやり方で signed も unsigned も扱うためには 1bit のムダ (しかも負の場合のみ) は仕方がなさげ。試験は eazy に以下

void test_scheme_Scm_MakeBignumFromUI(void)
{
    ScmObj obj;
    unsigned long v = 2147483647L;

    obj = Scm_MakeBignumFromUI(v);
    CU_ASSERT_TRUE(SCM_BIGNUMP(obj));
    CU_ASSERT_EQUAL(1, SCM_BIGNUM_SIZE(obj));
    CU_ASSERT_EQUAL(1, SCM_BIGNUM_SIGN(obj));
    CU_ASSERT_EQUAL(v, ((ScmBignum*)obj)->values[0]);

    v = 2147483648UL;

    obj = Scm_MakeBignumFromUI(v);
    CU_ASSERT_TRUE(SCM_BIGNUMP(obj));
    CU_ASSERT_EQUAL(1, SCM_BIGNUM_SIZE(obj));
    CU_ASSERT_EQUAL(1, SCM_BIGNUM_SIGN(obj));
    CU_ASSERT_EQUAL(v, ((ScmBignum*)obj)->values[0]);
}

ケツに UL とか L とか付けてなくて若干ハマりました (warning ではありましたが)。