にたまごほうれん草アーカイブ

はてなダイアリーで書いてた「にたまごほうれん草」という日記のアーカイブです。現在は「にたまごほうれん草ブログ」を運営中です。

flexで字句解析器を作る

コンパイラ入門をゆっくり勉強中。
字句解析(lex, flex)の章と構文解析(yacc, bison)の章に分かれているので、今日は字句解析のみ使って構文解析してみる。
最終的な目標は電卓の作成。
本の中で紹介されているコードを流用して、さらにリエントラントなスキャナを作成することにした。

  • scanner.l
%{
#include <math.h>
#include "scanner.h"
enum { ZERO = 0, ID, NUM, REAL, ADDOP, SUBOP, MULOP, DIVOP, LPAR, RPAR, ERROR };
%}
%option reentrant
%%
[_$a-zA-Z][_$0-9a-zA-Z]*            { return ID; }
0|[1-9][0-9]*                       { return NUM; }
([0-9]+"."[0-9]*|([0-9]*)"."[0-9]+) { return REAL; }
"+"    { return ADDOP; }
"-"    { return SUBOP; }
"*"    { return MULOP; }
"/"    { return DIVOP; }
"("    { return LPAR;  }
")"    { return RPAR;  }
"\n"|" "|"\t"           {}
"/*"[0-9a-zA-Z _$]*"*/" {}
.      { return ERROR; }
%%
int yywrap(yyscan_t yyscanner) { return 1; }

int mycalc_scanner() {
    yyscan_t scanner;
    int t;
    yylex_init(&scanner);
    while((t = yylex(scanner)) != 0) {
        printf("number = %d, string = '%s'\n", t, yyget_text(scanner));
    }
    yylex_destroy(scanner);
    return 0;
}

二つの「%%」に囲まれた行が、スキャン定義。ここでは簡単に変数、整数、実数、演算子、括弧、コメントの定義を行うようにした。
また、リエントラントにするために、

%option reentrant

の行を追加し、yylexの引数に構造体yyguts_tを指定するようにした。yyscanner_tという外部から使うための型がちゃんとあったので修正。*1
main関数は別にしているので、別途ヘッダファイルも作成した。(これは後述)
コンパイルし、実行すると標準入力から行を読み込み、スキャンした結果を表示する。
以下のような感じ。

$ ./src/mycalc
1 + 2
number = 2, string = '1'
number = 4, string = '+'
number = 2, string = '2'

autoconfを利用するために

configure.ac
AM_PROG_LEX

と書いた行を追加。*2

Makefile.am
bin_PROGRAMS = mycalc
mycalc_SOURCES = mycalc.c scanner.l

これで、makeを走らせると自動的にscanner.lからscanner.cを作成してくれるようになる。

その他のコード

書く必要があるかどうかわからないけど。

  • mycalc.c
#include <stdio.h>
#include "scanner.h"

int main()
{
    mycalc_scanner();
    return 0;
}
  • scanner.h
#ifndef SCANNER_H
#define SCANNER_H

int mycalc_scanner();

#endif

*1:このあたりのドキュメントが本当に少ない。世の中にはこの方法を使っている人は殆どいないということか、それとも間違っているのか間違ってた。ここを参考→)Init and Destroy Functions - Lexical Analysis With Flex

*2:それより前の、ビルド環境準備段階については、autoreconfを使って簡単にビルド環境を作るを参照