bisonとflexで列番号を知る方法

拙著「プログラミング言語を作る」は中国で翻訳出版されていて、先日、中国の読者さんから質問メールをもらいました(英語で)。
この質問者さんからメールをもらうのは2回目で、前回は1年ほど前、その時点で「I'm a middle school student in China.」とのことでした。優秀だなあ。


質問内容は、

エラーメッセージに行番号だけでなく列番号も出したいが、どうすれば取得できるか?

というもの。前回のメールおよび私の本の内容から彼*1はbisonとflexを使っていると思われるので、それを前提に回答しました。私自身やったことはなくて今回調べたので、せっかくなのでここに書いておきます。

まず、bisonでは、@n表記により、トークンの位置を取得できます。たとえば「プログラミング言語」で作っている型なし言語crowbarのパーサでは、以下のようにして関数の仮引数リストをパースしていますが、
http://kmaebashi.com/programmer/devlang/crowbar_src_0_1_01/S/2.html

parameter_list
        : IDENTIFIER
        {
            $$ = crb_create_parameter($1);
        }
        | parameter_list COMMA IDENTIFIER
        {
            $$ = crb_chain_parameter($1, $3);
        }
        ;

ひとまずfprintf()でトークンの列番号を表示するとしたら、以下のように書けます。

parameter_list
        : IDENTIFIER
        {
            $$ = crb_create_parameter($1);
            fprintf(stderr, "%s:%d..%d\n", $1, @1.first_column, @1.last_column); 
        }
        | parameter_list COMMA IDENTIFIER
        {
            $$ = crb_chain_parameter($1, $3);
            fprintf(stderr, "%s:%d..%d\n", $3, @3.first_column, @3.last_column); 
        }
        ;

@1とか@3とかは、$1とかと同様にルールの何番目かの記号を表していて、その型はLLYTYPE、y.tab.hでは以下の定義になっていました。

typedef struct YYLTYPE YYLTYPE;
struct YYLTYPE
{
  int first_line;
  int first_column;
  int last_line;
  int last_column;
}

これで、トークンの開始位置、終了位置が取れる…なら話は簡単なのですが、これを使うには当然lexerの協力が必要で、flexはこれをサポートしていません。そこで、自力で数え上げる必要があります。
とはいってもそう難しくはなくて、croabar.lをサンプルにするなら、
http://kmaebashi.com/programmer/devlang/crowbar_src_0_1_01/crowbar_0_1_01_l.html

以下のように書けます。

int current_column = 1; /* 現在の列位置を保持するグローバル変数 */
#define YY_USER_ACTION {\
  yylloc.first_column = current_column;\
  yylloc.last_column = current_column + yyleng - 1; \
  current_column += yyleng;
}
...
<INITIAL>\n {increment_line_number(); current_column = 1;}
...
<COMMENT>\n {
  increment_line_number();
  current_column = 1;
  BEGIN INITIAL;
}

current_columnというのは現在の列位置を保持するグローバル変数です(flexやbisonが決めたものではなく、私が定義)。
YY_USER_ACTIONというのは、flexにおいて各アクションの実行前に必ず実行される処理を定義するマクロです。そして、yylengというのは、flexが提供する、現在のトークンの長さを意味するグローバル変数です。
よって、YY_USER_ACTIONで列位置を数え上げていき、改行のたびにそれを1に戻せばよいわけです。

しかしまあ、英語で質問メールをもらったら返信も英語でしなければいけないわけで、私の英語力だと結構疲れますね……f(^^;

*1:中国人の名前は知らないので、彼女かもしれませんが。