本文不对词法和语法、以及 flex 和 bison 进行介绍,如有需要,可以阅读《 RPC 的实现 》。本文试图用直接的方式,以最短的篇幅介绍一个最简单的 IDL 编译器实现。
本文介绍的 IDL 编译器,能够解析如下所示的 IDL 文件,但限于篇幅,生成 C++ 代码部分省略掉,只介绍到对下述内容的解析,以便控制篇幅和复杂度。
下面这个表格为示例 IDL 文件 example.idl 的内容:
// Author: sky、木鱼
// Date: 2022/11/20
// 运行示例: ./idl_compiler example. idl
request
{
optional aaa: int16(0, 2015);
required bbb: string(0, 32);
}
response
{
required xxx: int32;
required zzz: string;
}
运行效果如下:
request ==>
int16 aaa (0, 2015)
string bbb (0, 32)
response ==>
int32 xxx (, )
string zzz (, )
request | 表示为请求 |
---|---|
response | 表示为响应 |
optional | 表示字段是可选的 |
required | 表示字段必须存在 |
int16 | 支持的数据类型,还包括 string 、 int32 和 int64 等 |
(0 , 2015) | 这个也是可选的,表示取值范围,对于整数则表示最小值和最大值,对于字符串则表示最小长度和最大长度 |
aaa | 为字段名称,其它如 bbb 、 xxx 和 zzz 等也是字段名称 |
文件名 | 文件说明 |
---|---|
example.idl | 演示用 IDL 文件 |
mooon.l | 词法文件 |
mooon.y | 语法文件 |
service_info.h | 定义存储元数据的结构 |
service_info.cpp | 对 service_info.h 的实现 |
main.cpp | main() 函数所在文件,调用解析器,并生成目标代码(本文为简单,并没有生成目标代码,而只是在屏幕上输出) |
Makefile | 编译脚本,成功后生成编译工具 idl_compiler |
定义 example.idl 的词法文件:
// Author: yijian
// Date: 2015/01/20
%option yylineno
// flex mooon.l
%{
#include "mooon.tab.h" // bison 编译 mooon.y 时生成的文件
#include " service_info.h "
// 定义保留关键词
void reserved_keyword(const char* keyword)
{
yyerror("Cannot use reserved language keyword: \"%s\"\n", keyword);
exit(1);
}
// 整数大小溢出判断
void integer_overflow(const char* text)
{
yyerror("This integer is too big: \"%s\"\n", text);
exit(1);
}
// 无效的单词
void unexpected_token(const char* text)
{
yyerror("Unexpected token: \"%s\"\n", text);
exit(1);
}
%}
// 下面的定义,类似于 C/C++ 语言中的宏定义,另外请注意本区域内的注释不能顶格
intconstant ([+-]?[0-9]+)
dubconstant ([+-]?[0-9]*(\.[0-9]+)?([eE][+-]?[0-9]+)?)
identifier ([a-zA-Z_][\.a-zA-Z_0-9]*)
whitespace ([ \t\r\n]*)
sillycomm ("/*""*"*"*/")
multicomm ("/*"[^*]"/"*([^*/]|[^*]"/"|"*"[^/])*"*"*"*/")
comment ("//"[^\n]*)
unixcomment ("#"[^\n]*)
symbol ([:;\,\{\}\(\)\=\[\]])
%%
// 下面是模式,以及模式对应的动作,也请注意本区域内的注释不能顶格
{whitespace} { }
{sillycomm} { }
{multicomm} { }
{comment} { }
{unixcomment} { }
"request" { yylval.sval = strdup(yytext); return tok_request; }
"response" { yylval.sval = strdup(yytext); return tok_response; }
"string" { yylval.sval = strdup(yytext); return tok_string; }
"bool" { yylval.sval = strdup(yytext); return tok_bool; }
"int16" { yylval.sval = strdup(yytext); return tok_int16; }
"int32" { yylval.sval = strdup(yytext); return tok_int32; }
"int64" { yylval.sval = strdup(yytext); return tok_int64; }
"double" { yylval.sval = strdup(yytext); return tok_double; }
"required" { yylval.sval = strdup(yytext); return tok_required; }
"optional" { yylval.sval = strdup(yytext); return tok_optional; }
{ symbol } { return yytext[0]; }
"service" { reserved_keyword(yytext); }
"struct" { reserved_keyword(yytext); }
{ intconstant } {
errno = 0;
yylval.iconst = strtoll(yytext, NULL, 10);
if (ERANGE == errno)
{
integer_overflow(yytext);
}
return tok_int_constant;
}
{ dubconstant } {
yylval.dconst = atof(yytext);
return tok_dub_constant;
}
{ identifier } { yylval.sval = strdup(yytext); return tok_identifier; }
. { unexpected_token (yytext); }
%%
// 如果定义了选项“ %option noyywrap ”,则不需要下面的 yywrap() 函数
int yywrap ()
{
return 1;
}
定义 example.idl 的语法文件:
// Author: yijian
// Date: 2015/01/20
%{
// bison -d mooon.y
#include " service_info.h "
static struct FieldInfo g_field_info;
static FieldInfoTable g_field_info_table(false);
%}
% union
{
int ival;
char* sval;
int64_t iconst;
double dconst;
}
// 为各 token 指定使用 union 的哪个成员来存储它的值
%token tok_int_constant
%token tok_dub_constant
%token tok_request
%token tok_response
%token tok_string
%token tok_bool
%token tok_int16
%token tok_int32
%token tok_int64
%token tok_double
%token tok_identifier
%token tok_required
%token tok_optional
%%
// 本区域的内容是语法解析定义
ServiceDef:
| RequestDef
| ResponseDef
| RequestDef ResponseDef
| ResponseDef RequestDef
;
RequestDef: tok_request '{' FieldDefList '}'
{
g_request_info.swap(g_field_info_table);
}
;
ResponseDef: tok_response '{' FieldDefList '}'
{
g_response_info.swap(g_field_info_table);
}
;
FieldDefList: FieldDef
| FieldDefList FieldDef
{
}
;
FieldDef: FieldRequiredness tok_identifier ':' FieldType Attributes ';'
{
g_field_info.name = $2;
g_field_info_table.add_field_info(g_field_info);
g_field_info.reset();
}
;
FieldRequiredness: tok_required
{
g_field_info.optional = false;
}
| tok_optional
{
g_field_info.optional= true;
}
FieldType: tok_string
{
g_field_info.type_name = $1;
}
| tok_bool
{
g_field_info.type_name = $1;
}
| tok_int16
{
g_field_info.type_name = $1;
}
| tok_int32
{
g_field_info.type_name = $1;
}
| tok_int64
{
g_field_info.type_name = $1;
}
| tok_double
{
g_field_info.type_name = $1;
}
;
Attributes:
| '(' MinValue ',' MaxValue ')'
{
}
MinValue: tok_int_constant
{
g_field_info.limit_type = LIMIT_INT;
g_field_info.limit.int_limit.max = $1;
}
| tok_dub_constant
{
g_field_info.limit_type = LIMIT_DOUBLE;
g_field_info.limit.dou_limit.max = $1;
}
;
MaxValue: tok_int_constant
{
g_field_info.limit_type = LIMIT_INT;
g_field_info.limit.int_limit.max = $1;
}
| tok_dub_constant
{
g_field_info.limit_type = LIMIT_DOUBLE;
g_field_info.limit.dou_limit.max = $1;
}
;
%%
定义了 FieldInfo 和 FieldInfoTalbe ,以供 mooon.y 使用。另外, main.cpp 需要根据 FieldInfo 和 FieldInfoTalbe 来生成目标代码:
// Author: yijian
// Date: 2015/01/20
#ifndef SERVICE_INFO_H
#define SERVICE_INFO_H
#include
#include
#include
#include
#include
#include
#include
// limit 类型
// 针对 optional aaa: int16(0, 2015);
// 如果 int16 后没有 (0, 2015) ,则为 LIMIT_NONE
// 对于 string 和各种 int ,则为 LIMIT_INT ,如 int16(0, 2015) 即为 LIMIT_INT
// 对于 double 则要使用 LIMIT_DOUBLE
enum LimitType
{
LIMIT_NONE,
LIMIT_INT,
LIMIT_DOUBLE
};
// 用来存储字段信息
struct FieldInfo
{
bool optional; // 表示字段是否为可选字段
std::string name; // 字段的名称,如示例中的 aaa 、 bbb 、 xxx 和 zzz
std::string type_name; // 字段的数据类型,如 int16 、 string 等
// 最大值(对于整数值)或最大长度(对于字符串值)
// 针对 int16(0, 2015) 中的 (0, 2015)
enum LimitType limit_type;
union
{
struct
{
int64_t max;
int64_t min;
} int_limit;
struct
{
double max;
double min;
} dou_limit;
} limit;
FieldInfo()
{
reset();
}
void reset()
{
limit_type = LIMIT_NONE;
}
};
// 字段信息表,用来存储一个 request 或一个 response 中所有字段的 FieldInfo
class FieldInfoTable
{
public:
typedef std::map<:string struct fieldinfo> FieldInfoMap;
public:
FieldInfoTable(bool is_request);
void reset();
void show();
void swap(FieldInfoTable& another);
const FieldInfoMap& get_field_info_map() const;
void add_field_info (const struct FieldInfo& field_info);
private:
bool _is_request;
FieldInfoMap _field_info_map;
};
extern FieldInfoTable g_request_info ;
extern FieldInfoTable g_response_info ;
template
std::string any2string(T t)
{
std::stringstream ss;
ss
return ss.str();
}
extern int yylineno;
extern char* yytext;
extern int yylex(void);
extern void yyerror(const char* format, ...);
#endif // SERVICE_INFO_H
以下内容是针对 service_info.h 的实现:
// Author: yijian
// Date: 2015/01/20
#include " service_info.h "
FieldInfoTable g_request_info (true);
FieldInfoTable g_response_info (false);
FieldInfoTable::FieldInfoTable(bool is_request)
: _is_request(is_request)
{
}
void FieldInfoTable::reset()
{
_field_info_map.clear();
}
void FieldInfoTable::show()
{
if (_field_info_map.empty())
{
printf("empty\n");
}
else
{
std::string type = _is_request? "request": "response";
printf("%s ==> \n", type.c_str());
for (FieldInfoMap::iterator iter=_field_info_map.begin();
iter!=_field_info_map.end(); ++iter)
{
const struct FieldInfo& field_info = iter->second;
std::string tag = field_info.optional? "optional": "required";
std::string min, max;
if (LIMIT_INT == field_info.limit_type)
{
min = any2string(field_info.limit.int_limit.min);
max = any2string(field_info.limit.int_limit.max);
}
else if (LIMIT_DOUBLE == field_info.limit_type)
{
min = any2string(field_info.limit.dou_limit.min);
max = any2string(field_info.limit.dou_limit.max);
}
printf("\t %s %s (%s, %s)\n",
tag.c_str(),
field_info.type_name.c_str(), field_info.name.c_str(),
min.c_str(), max.c_str());
}
}
}
void FieldInfoTable::swap(FieldInfoTable& another)
{
const FieldInfoMap& field_info_map = another.get_field_info_map();
for (FieldInfoMap::const_iterator iter=field_info_map.begin();
iter!=field_info_map.end(); ++iter)
{
const struct FieldInfo& field_info = iter->second;
add_field_info(field_info);
}
another.reset();
}
const FieldInfoTable::FieldInfoMap& FieldInfoTable::get_field_info_map() const
{
return _field_info_map;
}
void FieldInfoTable::add_field_info(const struct FieldInfo& field_info)
{
std::pair<:iterator bool> ret =
_field_info_map.insert(std::make_pair(field_info.name, field_info));
if (!ret.second)
{
yyerror("Syntax error: repeat field: in request\n", field_info.name.c_str());
exit(1);
}
}
void yyerror(const char* format, ...)
{
fprintf(stderr, "[ERROR:%d] (last token was '%s')\n", yylineno, yytext);
va_list args;
va_start(args, format);
vfprintf(stderr, format, args);
va_end(args);
}
yyparse () 会将解析结果存储在 g_request_info 和 g_response_info 这两个全局对象中,根据 g_request_info 和 g_response_info 存储的信息即可生成目标代码。
// Author: yijian
// Date: 2015/01/20
#include "service_info.h"
extern int yyparse();
int main(int argc, char* argv[])
{
yyparse ();
g_request_info. show ();
g_response_info. show ();
return 0;
}
编译脚本,运行成本后生成 IDL 编译器 idl_compiler :
# Author: yijian
# Date: 2015/01/20
all: idl_compiler
objs=service_info.o mooon.tab.o lex.yy.o main.o
idl_compiler : $(objs)
g++ -g -o $@ $(objs)
service_info.o: service_info.h service_info.cpp
g++ -g -c service_info.cpp -I.
mooon.tab.o: mooon.tab.c
g++ -g -c mooon.tab.c -I.
mooon.tab.c: mooon.y
bison -d mooon.y
lex.yy.o: lex.yy.c mooon.tab.h
g++ -g -c lex.yy.c -I.
lex.yy.c: mooon.l
flex mooon.l
main.o: main.cpp service_info.h mooon.l mooon.y
g++ -g -c main.cpp -I.
.PHONY: clean
clean:
rm -f $(objs)
rm -f mooon.tab.c
rm -f lex.yy.c
rm -f idl_compiler
执行 make 编译,成功后运行: ./ idl_compiler example.idl ,即可以观察到屏幕输出如下:
request ==>
int16 aaa (0, 2015)
string bbb (0, 32)
response ==>
int32 xxx (, )
string zzz (, )