ぎゃあ

svn から checkout した master copy で作業してた。後から svn diff して patch -R だな…っていうかこれが「普通の」やり方だったりするんだろうか。

追記: あれ、local に diff に必要な情報が入ってるんですか。左様ですか。Manual ぐらい読もうっと…

エラー処理

エラー処理をどうしようか。こういう感じに message を出力したいわけですが:

Scm_ErrorObj (const char *func_name, ScmObj obj,
              const char *fmt, ...)
{
  fputs (func_name, err_port);
  vfprintf (err_port, fmt, va_args);
  WriteToPort (obj, err_port);
}

func_name の受け渡しを透過的にやらないと意味が無い。受け渡し方法もできれば stack 渡しと大域変数渡しぐらいは切り替えられるようにしたい。
単純に #define ERR Scm_ErrorObj しただけだと func_name をこっそり引数リストに追加することができないし、func_name だけ他の関数で出力しようとすると caller のコードが膨れる。大域変数渡しで決め打ちするしかないかなぁ。
とりあえず variadic を諦めてみた。吉と出るか凶と出るか。出るなら凶か。

こっそり POP_ARG() の引数を保存する件

POP_ARG() の引数の方を破壊することにした。こっちの方が automagical でなくなっていい感じ。

POP_ARG(args)  (args = CDR(args), orig_car)

きっと何の事やら誰にも分からないことでしょう。

File 内の区分け

前から思ってたのに今ごろ感たっぷりで恐縮ですが、

/*===================
  Macro Declarations
  ===================*/
#define foo bar
#define bar baz
...

的な区分けは良くないと思う。「C 言語的に何をしているか」ではなくて「何を書いてるか」でわけた方がよい。例えば

/*========================
  Object location handling
  ========================*/
typedef ScmObj *ScmRef;
#define SCM_REF_CAR(cons) (&SCM_CAR(cons))
#define SCM_REF_CDR(cons) (&SCM_CDR(cons))
#define SCM_DEREF(ref)    (*(ref))
/* RFC: Is there a better name? */
#define SCM_SET(ref, obj) (*(ref) = (obj))

とか。これだと ScmRef がそもそも何で存在してるのかが一目瞭然だし、例えば SCM_REF_CAR() を変更して ScmRef をそれに追随させるときにいちいち search する必要が無い。関連する変更箇所が近いので、変え忘れが減る。
私も前者のように構文で分けてた時期があったけど、結局きっちりしてる「感じ」が出るだけで可読性は下がる思い。
ただ、なんでもかんでも処理内容で section 分けするのも考え物で、例えば関数 prototype なんかは compiler の型チェックを正常に動かすためのやっつけ仕事であることが多いので、それだけまとめてもいいかもしれない。その場合でも「全関数」をまとめるとわかりにくい書き方を (自分に!) 強制してしまいがち。

Preview release

いくつかの関数で試し斬り。結局 TRY_POP ばっかり使うので、TRY_POP → POP_ARG and POP (alias), POP_ARG → MUST_POP_ARG に改名。

ScmObj ScmExp_begin(ScmObj args, ScmEvalState *eval_state)
{
    ScmObj env = eval_state->env;
    ScmObj expr;
    DECLARE_FUNCTION("begin", SyntaxVariadic0);

    /* sanity check */
    if (NULLP(lst))
        return SCM_UNDEF;

    while (expr = TRY_POP_ARG(args), CONSP(args))
	EVAL(expr, env);

    /* 多分後で何とかする */
    if (!NULLP(args))
        ERR("improper argument list", args);

    /* Return tail expression. */
    return expr;
}

ScmObj ScmExp_if(ScmObj test, ScmObj conseq,
                 ScmObj rest, ScmEvalState *eval_state)
{
    ScmObj env  = eval_state->env;
    ScmObj alt;
    DECLARE_FUNCTION("if", SyntaxVariadic0);

    if (NFALSEP(EVAL(test, env)))
        return conseq;
    else {
	alt = TRY_POP_ARG(rest);
#if SCM_STRICT_ARGCHECK
	ASSERT_NO_MORE_ARG(rest);
#endif
        return VALIDP(alt) ? alt : SCM_UNDEF;
    }
}
ScmObj ScmOp_number2string (ScmObj num, ScmObj args)
{
...
    DECLARE_FUNCTION("number->string", ProcedureVariadic1);

    ASSERT_INTP(num);
    n = SCM_INT_VALUE(num);

    /* r = radix */
    if (NO_MORE_ARG(args))
	r = 10;
    else {
	radix = POP_ARG(args);
	ASSERT_INTP(radix);
	r = SCM_INT_VALUE(radix);

#if SCM_STRICT_R5RS
        if (!(r == 2 || r == 8 || r == 10 || r == 16))
#else
        if (!(2 <= r && r <= 16))
#endif
            ERR("invalid or unsupported radix", radix);
#if SCM_STRICT_ARGCHECK
	ASSERT_NO_MORE_ARG(args);
#endif
    }
...
}

RFC: 戻り値

そろそろ授業なので一つ投げておく。

before: while (car = POP_ARG(args), VALIDP(car))
after : while ((car = POP_ARG(args)))

見ての通り、POP_ARG() の仕様に、「引数がもう無いときの戻り値は NULL (or 0)」というのを追加すると使用側のコードがとても簡単になる。しかしこれをすると SCM_NULL == NULL とかの変更はもうできない。
別の方法として、VALIDP() が引数を評価する回数はたかだか一回という制約を設けると while (VALIDP(car = POP_ARG(args))) という書き方もできる。
どっちも当面問題は出ないけど、決定すると恐らくバグを出さずに後戻りするのが難しい。そこまでして条件文を簡略化する必要があるだろうか。