If a C programmer asks "do you want to see something cool?", run away.
--John Van Enk

Tuesday, November 20, 2007

Зачем мне нужен any_fun_ptr

В предыдущем посте, я высказался о том, что хочу эдакий generalized callback, который мог бы указывать на любую функцию. Возникает вопрос, а зачем это мне? Попробую для начала сделать некоторое введение, которое, надеюсь, немного прольет свет на эту проблему.
Давайте рассмотрим простенький калькулятор, поддерживающий 26 переменных (a-z), операции +, -, *, / и скобки, имлементированный при помощи парочки Flex/Bison и с целевым языком С++. Как я уже когда-то писал, для начала необходимо сделать небольшой workaround для того, чтобы состыковать парсер, сгенерированный бизоном, и лексер, сгенерированный флексом. Для этого используется класс наследник от yyFlexLexer.
файл lexer.h
#ifndef _LEXER_DERIVED_CLASS_H_
#define _LEXER_DERIVED_CLASS_H_
#ifndef __FLEX_LEXER_H
#undef yyFlexLexer
#include <FlexLexer.h>
#endif
#include <iostream>
#include <parser.h>
namespace calc{
class Lexer:public yyFlexLexer {
int yylex();
Parser::semantic_type* yylval;
Parser::location_type* yylloc;
public:
Lexer( std::istream*src = 0,std::ostream* out=0 );
virtual ~Lexer();
Parser::token_type operator()
(Parser::semantic_type* lval, Parser::location_type* lloc=0);
void LexerError(const char* msg);

};
}
#endif

файл лексического анализатора - lexer.lpp
%option 8bit
%option noyywrap
%option noyylineno
%option caseful
%option c++
%option yyclass="calc::Lexer"
%option pointer
%{
#include <lexer.h>
%}

%%

[a-z] {
yylval->var = *yytext - 'a';
return Parser::token::VARIABLE;
}

[0-9]+ {
yylval->number = atoi(yytext);
return Parser::token::INTEGER;
}

[-+()=/*\n] { return *yytext; }

[ \t] ; /* skip whitespace */

. LexerError("Unknown character");

%%
namespace calc{

Lexer::Lexer( std::istream*src,std::ostream* out ):yyFlexLexer(src,out){}

Lexer::~Lexer(){}

Parser::token_type Lexer::operator()
(Parser::semantic_type* lval, Parser::location_type* lloc){
yylval=lval; yylloc=lloc;
return Parser::token_type(yylex());
}

void Lexer::LexerError(const char* msg){
std::cerr<<"Error: "<<msg<<std::endl;
}

}

файл синтаксического анализатора - parser.ypp
%skeleton "lalr1.cc"
%name-prefix="calc"
%parse-param {calc::Lexer& yylex}
%define "parser_class_name" "Parser"
%{
#ifdef yylex
#undef yylex
#endif
namespace calc{
class Lexer;
};
%}
%union{
int number;
char var;
}
%{
#include <lexer.h>
#include <iostream>
int sym[26];
%}
%token <number> INTEGER
%token <var> VARIABLE
%type <number> expression
%left '+' '-'
%left '*' '/'

%%

program:
program statement '\n'
| /* NULL */
;

statement:
expression { printf("%d\n", $1); }
| VARIABLE '=' expression { sym[$1] = $3; }
;

expression:
INTEGER
| VARIABLE { $$ = sym[$1]; }
| expression '+' expression { $$ = $1 + $3; }
| expression '-' expression { $$ = $1 - $3; }
| expression '*' expression { $$ = $1 * $3; }
| expression '/' expression { $$ = $1 / $3; }
| '(' expression ')' { $$ = $2; }
;

%%
void calc::Parser::error (const location_type& loc, const std::string& msg){
std::cerr<<"Error: "<<msg<<std::endl;
};

main
#ifdef HAVE_CONFIG_H
#include <config.h>
#endif

#include <iostream>
#include <cstdlib>
#include <lexer.h>
#include <parser.h>
using namespace std;

int main(int argc, char *argv[])
{
calc::Lexer lexer(&cin,&cout);
calc::Parser parser(lexer);
parser.parse();
return EXIT_SUCCESS;
}


А теперь допустим, что мы захотели, чтобы калькулятор поддерживал еще и некоторые функции, скажем sin, cos, tan, atan, atan2 и иже с ними.

Можно решить это в лоб:
в лексере написать

.................
sin { return Parser::token::SIN;}
.................
atan2 { return Parser::token::ATAN2;}
.................

а в парсере

.................
expression:
INTEGER
| VARIABLE { $$ = sym[$1]; }
| expression '+' expression { $$ = $1 + $3; }
| expression '-' expression { $$ = $1 - $3; }
| expression '*' expression { $$ = $1 * $3; }
| expression '/' expression { $$ = $1 / $3; }
| '(' expression ')' { $$ = $2; }
| SIN '(' expression ')' { $$=sin($3);}
| COS '(' expression ')' { $$=cos($3);}
.................
| ATAN2 '(' expression ',' expression ')' { $$=atan2($3,$5);}
;
.................


А можно и по другому:
в лексере написать

.................
func ([a-zA-z]+)([0-9]*)
%%
{func} {
yylval->func_ptr = &func_map[yytext];
return Parser::token::FUNC;}

а в парсере

.................
%union{
int number;
char var;
any_fun_ptr* func_ptr;
}
%%
.................
expression:
INTEGER
| VARIABLE { $$ = sym[$1]; }
| expression '+' expression { $$ = $1 + $3; }
| expression '-' expression { $$ = $1 - $3; }
| expression '*' expression { $$ = $1 * $3; }
| expression '/' expression { $$ = $1 / $3; }
| '(' expression ')' { $$ = $2; }
| FUNC '(' expression ')' { $$=$1($3);}
| FUNC '(' expression ',' expression ')' { $$=$1($3,$5);}
;
................



Сам же func_map может быть, например, банальным std::map<std::string, any_fun_ptr> func_map;
А адреса функций заносятся в него при старте скажем.
Вот собственно несколько упрощенное описание того, зачем это может быть нужно.