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を利用するために
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を使って簡単にビルド環境を作るを参照