动态配置脚本实现之 flex,yacc应用
2015-06-07 23:59
531 查看
之前安装kamailio试验的时候,看到其配置方式类似一个脚本方式控制call的流程,如下面的例子,
loadmodule "../sip_router/modules/usrloc/usrloc.so"
modparam("usrloc", "db_mode", 0)
...
if(!lookup("location"))
{
...
sl_reply_error();
...
}
kamailio启动会加载usrloc.so并通过这个模块提供的方法lookup判断做出call流程的控制。
这样做的好处,
1. 各个模块的开发相互独立,只需要编译一个动态库,并export 一些方法就可以。
2. 特性开发更容易,只需要配置不同的行为脚本就能实现不同的功能。(这一点上感觉如果同时开启配置的功能特别多的时候,并且policy比较多的情况下,不一定比hard code 的方式开发feature 更容易调试配置,不知道这样想法对不对?)
这里主要用到了两种技术:
1. 是 flex,yacc生成的语法解析来解析控制脚本,将控制脚本生成一个内部表示.
2. 动态加载动态链接库so,并提取方法。
这里将实现一个小的例子来示例flex,yacc在这种场景下的引用:
实现目标: 输入一个数字, 然后根据配置脚本做算术运算得到输出,加载一个动态链接库提取其方法,
例如配置表达式:factorial($input+ extadd($input))
动态链接库 extmath 提供方法 factorial(n) = 1*2*...*n; extadd(n) = n + 1
例如输入1, 输出结果:6
第一部分定义 语法此法分析器
1.1. 定义词法分析器,word.lex
word.lex 定义如下,为了简答我们只考虑+,- 运算以及 不考虑空格
truman@ubuntu-ezoucai:~/work/lex_yacc/flex_yacc_dynamic_config$ cat word.lex
%{
#include <stdio.h>
#include <string.h>
#include "grammar.tab.h"
int count = 0;
%}
LETTER [a-zA-Z]
DIGIT [0-9]
DECNUMBER 0|([1-9]{DIGIT}*)
FUNCNAME {LETTER}+
%%
{DECNUMBER} { yylval.intval = atoi(yytext); return INTVAL; }
{FUNCNAME} { yylval.strval = strdup(yytext); return FUNCNAMEVAL;}
[\+\-\(\)] { return yytext[0];}
"$input" { return INPUT;}
%%
int yywrap()
{
return 1;
}
1.2. 定义语法分析器yacc文件grammar.y
truman@ubuntu-ezoucai:~/work/lex_yacc/flex_yacc_dynamic_config$ cat grammar.y
%{
#include <stdlib.h>
#include <stdio.h>
int yylex(void);
void yyerror(const char *msg);
#define YYERROR_VERBOSE 1
%}
%token INT INPUT FUNCNAME
%left '+' '-'
%left '*' '/'
%union
{
int intval;
char *strval;
// struct rval_expr* rval_expr;
}
%token<intval> INTVAL
%token<strval> FUNCNAMEVAL
%%
program:
program statement
|
;
statement:
expr
;
expr:
INTVAL { printf("int value: %d\n", $1);}
| INPUT { ; }
| expr '+' expr { ; }
| expr '-' expr { ; }
| '(' expr ')' { ; }
| FUNCNAMEVAL '(' expr ')' { printf("Function name: %s\n", $1); }
;
%%
1.3. 定义main 文件
#include <stdio.h>
void yyparse();
int main(void) {
yyparse();
return 0;
}
void yyerror(const char *msg){
printf("%s\n", msg);
}
1.4. makefile 文件和 测试文件
truman@ubuntu-ezoucai:~/work/lex_yacc/flex_yacc_dynamic_config$ cat Makefile
all : grammar.y word.lex main.c
bison -d grammar.y
flex word.lex
g++ grammar.tab.c lex.yy.c main.c
test: all
./a.out < userdef.txt
truman@ubuntu-ezoucai:~/work/lex_yacc/flex_yacc_dynamic_config$ cat userdef.txt
factorial($input-1+extadd($input))
这里我们我们的程序已经能够分析我们定义的表达式,但是还没有做具体的工作,因为在语法文件grammy.y 中我们只是打印除了分析到的一部分内容,为了在运行过程中更快,我们需要在语法解析器中添加代码,使其能生成一个链表来保存表达式要进行的运算。
第二部分表达式程序内部表示
2.1 使用c++的继承多态来组合不同表达式,基类如下,
这里只需要实现四种 IntExpr,AddExpr,SubExpr,FuncExpr 子类就可以。
class Expr{,
public:
virtual int getValue() = 0;
};
2.1 在grammar.y 添加在语法解析过程中生成 Expr的代码
代码如下
statement:
statement expr { Executor::Add($2); }
|
;
expr: INTVAL { $$= new IntExpr($1);}
| INPUT { $$ = new IntExpr(GetInput());}
| expr '+' expr { $$ = new AddExpr($1, $3); }
| expr '-' expr { $$ = new SubExpr($1, $3); }
| '(' expr ')' { $$ = $2; }
| FUNCNAMEVAL '(' expr ')' {
FuncType* f = FuncLoader::SearchFunction($1);
if(f == NULL) {
char buf[100];
snprintf(buf, sizeof(buf), "can not find function %s\n", $1);
buf[sizeof(buf) -1 ] = '\0';
$$ = NULL;
yyerror(buf);
}
else $$ = new FuncExpr(f, $3);
}
;
这里由一下几点在我实际编写代码中话费了比较长的时间去研究:
1. 使用%union 使flex,yacc 在解析过程中能传递不同类型的值
2. %token<intval> 和 %type<expr> 的使用定义 各种token 和子表达范式 在union中使用的值
3. 外部类的头文件在flex文件中要添加在 #include "grammar.tab.h" 之前
github 代码:
https://github.com/trumanz/flex_yacc_dynamic_config
下一次继续总结动态加载库和导出函数的方法。
loadmodule "../sip_router/modules/usrloc/usrloc.so"
modparam("usrloc", "db_mode", 0)
...
if(!lookup("location"))
{
...
sl_reply_error();
...
}
kamailio启动会加载usrloc.so并通过这个模块提供的方法lookup判断做出call流程的控制。
这样做的好处,
1. 各个模块的开发相互独立,只需要编译一个动态库,并export 一些方法就可以。
2. 特性开发更容易,只需要配置不同的行为脚本就能实现不同的功能。(这一点上感觉如果同时开启配置的功能特别多的时候,并且policy比较多的情况下,不一定比hard code 的方式开发feature 更容易调试配置,不知道这样想法对不对?)
这里主要用到了两种技术:
1. 是 flex,yacc生成的语法解析来解析控制脚本,将控制脚本生成一个内部表示.
2. 动态加载动态链接库so,并提取方法。
这里将实现一个小的例子来示例flex,yacc在这种场景下的引用:
实现目标: 输入一个数字, 然后根据配置脚本做算术运算得到输出,加载一个动态链接库提取其方法,
例如配置表达式:factorial($input+ extadd($input))
动态链接库 extmath 提供方法 factorial(n) = 1*2*...*n; extadd(n) = n + 1
例如输入1, 输出结果:6
第一部分定义 语法此法分析器
1.1. 定义词法分析器,word.lex
word.lex 定义如下,为了简答我们只考虑+,- 运算以及 不考虑空格
truman@ubuntu-ezoucai:~/work/lex_yacc/flex_yacc_dynamic_config$ cat word.lex
%{
#include <stdio.h>
#include <string.h>
#include "grammar.tab.h"
int count = 0;
%}
LETTER [a-zA-Z]
DIGIT [0-9]
DECNUMBER 0|([1-9]{DIGIT}*)
FUNCNAME {LETTER}+
%%
{DECNUMBER} { yylval.intval = atoi(yytext); return INTVAL; }
{FUNCNAME} { yylval.strval = strdup(yytext); return FUNCNAMEVAL;}
[\+\-\(\)] { return yytext[0];}
"$input" { return INPUT;}
%%
int yywrap()
{
return 1;
}
1.2. 定义语法分析器yacc文件grammar.y
truman@ubuntu-ezoucai:~/work/lex_yacc/flex_yacc_dynamic_config$ cat grammar.y
%{
#include <stdlib.h>
#include <stdio.h>
int yylex(void);
void yyerror(const char *msg);
#define YYERROR_VERBOSE 1
%}
%token INT INPUT FUNCNAME
%left '+' '-'
%left '*' '/'
%union
{
int intval;
char *strval;
// struct rval_expr* rval_expr;
}
%token<intval> INTVAL
%token<strval> FUNCNAMEVAL
%%
program:
program statement
|
;
statement:
expr
;
expr:
INTVAL { printf("int value: %d\n", $1);}
| INPUT { ; }
| expr '+' expr { ; }
| expr '-' expr { ; }
| '(' expr ')' { ; }
| FUNCNAMEVAL '(' expr ')' { printf("Function name: %s\n", $1); }
;
%%
1.3. 定义main 文件
#include <stdio.h>
void yyparse();
int main(void) {
yyparse();
return 0;
}
void yyerror(const char *msg){
printf("%s\n", msg);
}
1.4. makefile 文件和 测试文件
truman@ubuntu-ezoucai:~/work/lex_yacc/flex_yacc_dynamic_config$ cat Makefile
all : grammar.y word.lex main.c
bison -d grammar.y
flex word.lex
g++ grammar.tab.c lex.yy.c main.c
test: all
./a.out < userdef.txt
truman@ubuntu-ezoucai:~/work/lex_yacc/flex_yacc_dynamic_config$ cat userdef.txt
factorial($input-1+extadd($input))
这里我们我们的程序已经能够分析我们定义的表达式,但是还没有做具体的工作,因为在语法文件grammy.y 中我们只是打印除了分析到的一部分内容,为了在运行过程中更快,我们需要在语法解析器中添加代码,使其能生成一个链表来保存表达式要进行的运算。
第二部分表达式程序内部表示
2.1 使用c++的继承多态来组合不同表达式,基类如下,
这里只需要实现四种 IntExpr,AddExpr,SubExpr,FuncExpr 子类就可以。
class Expr{,
public:
virtual int getValue() = 0;
};
2.1 在grammar.y 添加在语法解析过程中生成 Expr的代码
代码如下
statement:
statement expr { Executor::Add($2); }
|
;
expr: INTVAL { $$= new IntExpr($1);}
| INPUT { $$ = new IntExpr(GetInput());}
| expr '+' expr { $$ = new AddExpr($1, $3); }
| expr '-' expr { $$ = new SubExpr($1, $3); }
| '(' expr ')' { $$ = $2; }
| FUNCNAMEVAL '(' expr ')' {
FuncType* f = FuncLoader::SearchFunction($1);
if(f == NULL) {
char buf[100];
snprintf(buf, sizeof(buf), "can not find function %s\n", $1);
buf[sizeof(buf) -1 ] = '\0';
$$ = NULL;
yyerror(buf);
}
else $$ = new FuncExpr(f, $3);
}
;
这里由一下几点在我实际编写代码中话费了比较长的时间去研究:
1. 使用%union 使flex,yacc 在解析过程中能传递不同类型的值
2. %token<intval> 和 %type<expr> 的使用定义 各种token 和子表达范式 在union中使用的值
3. 外部类的头文件在flex文件中要添加在 #include "grammar.tab.h" 之前
github 代码:
https://github.com/trumanz/flex_yacc_dynamic_config
下一次继续总结动态加载库和导出函数的方法。
相关文章推荐
- Linux - dnsmasq - dns cache
- Linux: 使用screen管理你的远程会话
- oracle 链接使用
- iOS开发拓展篇—音频处理(音乐播放器4)
- iOS开发拓展篇—音频处理(音乐播放器2)
- iOS开发拓展篇—音频处理(音乐播放器3)
- Javascript(Array)整理
- 【转】项目奖金分配
- 安卓开发中防止内存溢出
- Wpf 脑力风暴游戏开发
- iOS开发拓展篇—音频处理(音乐播放器1)
- linux基础—分区与目录
- LeetCode 3 Longest Substring Without Repeating Characters 解题报告
- Linux系统中常用操作命令
- xcode教程,MAC常用命令
- oracle merge和批量insert实操
- iOS开发拓展篇—CoreLocation地理编码
- xcode教程,MAC常用命令
- vmware-vdiskmanager
- horizontalscrollview