您的位置:首页 > 其它

动态配置脚本实现之 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

下一次继续总结动态加载库和导出函数的方法。
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: