yacc や lex をつかっていても「なんかよくわからんけどうごく」という状態になりがちだったり、グローバル変数にまみれたりしがちだが、re2c + lemon だとそのへんがすっきりする。
レキサを以下のようにかく。yyfill を自前でかかなければいけないのがちょっと面倒だが、このようなクラスを手軽にかけるのはやはり便利である。flex ではこうはいかないのだ。
#ifndef CALC_SCANNER_H_
#define CALC_SCANNER_H_
#include <stdio.h>
#include <string.h>
#include <string>
#include <sstream>
#include <vector>
#include <iostream>
#include <fstream>
#include "scanner.def.h"
#include "parser.h"
class Scanner {
private:
// iostream sucks. very slow.
std::istream *ifs;
// buffer memory
char* m_buffer;
// current position
char* m_cursor;
char* m_limit;
char* m_token;
char* m_marker;
int m_buffer_size;
int m_lineno;
public:
void increment_line_number() {
m_lineno++;
}
Scanner( std::istream *ifs_, int init_size=1024 )
: m_buffer(0)
, m_cursor(0)
, m_limit(0)
, m_token(0)
, m_marker(0)
, m_buffer_size(init_size)
, m_lineno(1)
{
m_buffer = new char[m_buffer_size];
m_cursor = m_limit = m_token = m_marker = m_buffer;
ifs = ifs_;
}
~Scanner() {
delete [] m_buffer;
}
bool fill(int n) {
// is eof?
if (ifs->eof()) {
if ((m_limit-m_cursor) <= 0) {
return false;
}
}
int restSize = m_limit-m_token;
if (restSize+n >= m_buffer_size) {
// extend buffer
m_buffer_size *= 2;
char* newBuffer = new char[m_buffer_size];
for (int i=0; i<restSize; ++i) { // memcpy
*(newBuffer+i) = *(m_token+i);
}
m_cursor = newBuffer + (m_cursor-m_token);
m_token = newBuffer;
m_limit = newBuffer + restSize;
delete [] m_buffer;
m_buffer = newBuffer;
} else {
// move remained data to head.
for (int i=0; i<restSize; ++i) { //memmove( m_buffer, m_token, (restSize)*sizeof(char) );
*(m_buffer+i) = *(m_token+i);
}
m_cursor = m_buffer + (m_cursor-m_token);
m_token = m_buffer;
m_limit = m_buffer+restSize;
}
// fill to buffer
int read_size = m_buffer_size - restSize;
ifs->read( m_limit, read_size );
m_limit += ifs->gcount();
return true;
}
std::string text() {
return std::string( m_token, m_token+length() );
}
int length() {
return (m_cursor-m_token);
}
int lineno() {
return m_lineno;
}
int scan(YYSTYPE& yylval) {
std:
m_token = m_cursor;
/*!re2c
re2c:define:YYCTYPE = "char";
re2c:define:YYCURSOR = m_cursor;
re2c:define:YYMARKER = m_marker;
re2c:define:YYLIMIT = m_limit;
re2c:define:YYFILL:naked = 1;
re2c:define:YYFILL@len = #;
re2c:define:YYFILL = "if (!fill(#)) { return 0; }";
re2c:yyfill:enable = 1;
re2c:indent:top = 2;
re2c:indent:string=" ";
INTEGER = [1-9][0-9]*;
WS = [ \r\n\t\f];
ANY_CHARACTER = [^];
INTEGER {
yylval.int_value = atoi(this->text().c_str());
return TOKEN_INT;
}
"+" { return TOKEN_ADD; }
"-" { return TOKEN_SUB; }
"*" { return TOKEN_MUL; }
"/" { return TOKEN_DIV; }
WS {
goto std;
}
ANY_CHARACTER {
printf("unexpected character: '%c(%d)'\n", *m_token, *m_token);
goto std;
}
*/
}
};
#endif // CALC_SCANNER_H_
yylval とパーザの状態をあらわすクラスを定義する。YYSTYPE という名前は yacc からもらっている。もっといい名前のつけかたがある気がするが。
#ifndef CALC_SCANNER_DEF_H_
#define CALC_SCANNER_DEF_H_
typedef union {
int int_value;
} YYSTYPE;
struct ParserState {
int result;
ParserState() :result(0) {
}
};
#endif // CALC_SCANNER_DEF_H_
パーザは以下のように書く。%extra_argument で、パーザにたいする追加の引数を定義できる。AST を返したい場合などはこれを介してやればスレッドセーフとなるだろう。もはやグローバル変数をつかう必要はないのだ。
%token_prefix TOKEN_
%left ADD SUB.
%left MUL DIV.
%token_type { YYSTYPE }
%extra_argument { ParserState *state }
%include {
#include <iostream>
#include "scanner.def.h"
}
%syntax_error {
fprintf(stderr, "Syntax error\n");
}
%parse_failure {
fprintf(stderr,"Giving up. Parser is hopelessly lost...\n");
}
%start_symbol program
program ::= expr(A). {
state->result = A.int_value;
}
expr(A) ::= primary_expression(B). {
A.int_value = B.int_value;
}
expr(A) ::= expr(B) SUB primary_expression(C). {
A.int_value = B.int_value - C.int_value;
}
expr(A) ::= expr(B) ADD primary_expression(C). {
A.int_value = B.int_value + C.int_value;
}
expr(A) ::= expr(B) DIV primary_expression(C). {
A.int_value = B.int_value / C.int_value;
}
expr(A) ::= expr(B) MUL primary_expression(C). {
A.int_value = B.int_value * C.int_value;
}
primary_expression(A) ::= INT(B). {
A.int_value = B.int_value;
}
さてこのパーザとレキサをうごかす main プログラムがこちらだ。まったくグローバル変数をつかっていないし、パーザとレキサの間の関係性が非常に明確である。つなぎこみ部分を自分でかけるので可能性は無限大だ。
#include <sstream>
#include <cassert>
#include <cstdlib>
#include "scanner.h"
#include "parser.c"
int main() {
YYSTYPE yylval;
Scanner scanner(&std::cin);
void *pParser = ParseAlloc(malloc);
int tokenID;
#if 0
ParseTrace(stderr, (char*)"[Parser] >> ");
#endif
ParserState state;
// scanner.scan return 0 when get EOF.
while (tokenID = scanner.scan(yylval)) {
// printf("GET TOKEN: %d\n", tokenID);
Parse(pParser, tokenID, yylval, &state);
}
Parse(pParser, 0, yylval, &state);
ParseFree(pParser, free);
printf("RESULT: %d\n", state.result);
return 0;
}
今回は手抜きなので parser はクラスになっていないが、これも非常に簡単にクラスにできる。lemon は lempar.c というテンプレートを元にパーザを生成しているので、このファイルをちょっといじればよいのだ。
lemon + re2c だと lex + yacc よりとてもスマートにかけるので、今後はこのくみあわせでいこうとおもっている。