Parser Generator的使用说明
最近1个星期,大致学习了一下lex,虽然在windows系统上它并没有我所期望的强大,在调试和编写代码都遇到了不少困难,但是总体来说Parser Generator还是让我体会到了lex编程的快捷, 为了自己加深印象把一些参考资料和认识总结了一下.
<!--[if !supportLists]-->1. <!--[endif]-->parser generator是什么?
它是一款在windows下编写lex/yacc程序的工具.可以到
http://www.bumblebeesoftware.com/downloads.htm
下载
<!--[if !supportLists]-->2. <!--[endif]-->parser generator的使用方法
这里只以vc6.0为例,首先打开parser generator编辑器,选择Project->LibBuilder
在LibBuilder对话框中选中Visual C++(32-bit),按属性键Properties后确以下设置
Script file name ./Cpp/Script/msvc32.lbs
Name Visual C++(32-bit)
Directory msvc32
Compiler Version Version 6
Unicode True
Treat wchar_t as Built-in Type False
Compiler Bin Directory 安装路径/Microsoft Visual Studio/Vc98/bin
Compiler Bin Directory(2) 安装路径/Microsoft Visual Studio/Common/MSDev98/bin
Compiler Include Directory 安装路径/Microsoft Visual Studio/Vc98/include
Compiler Include Directory(2) 无
Compiler Library Directory 安装路径/Microsoft Visual Studio/Vc98/lib
Compiler Library Directory(2) 无
Libraries下的库文件全部选中后ok
LibBuilder对话框->Build(编译过程可能几分钟)
编译完成后我们就可以使用parser generator编写lex或是yacc程序了
Project->ParserWizard
Step1 工程设定(一点需要注意语言可以选择c或是c++或java)
Step2 工程设定(默认创建带main函数的yacc文件和lex文件)
Step3 yacc文件设定
Step4 lex文件设定
Lex和yacc的语法参考http://www.ibm.com/developerworks/cn/linux/sdk/lex/
编辑好代码后Project->RebBuild All在你创建好的工程下自动生成Step1选定语言的文件(.h/..c/.cpp/.java)
之后在vc6.0加入如下设置
Tool->Option-> directory
Bin file :
安装目录/PARSER GENERATOR 2/BIN
Include file:
安装目录/PARSER GENERATOR 2/CPP/INCLUDE
Library file
安装目录/PARSER GENERATOR 2/CPP/LIB/MSVC32
Soure file
安装目录/PARSER GENERATOR 2/CPP/SOURCE
创建vc6.0工程
将生成文件复制到vc6.0创建工程下
Source files和Header Files中加入生成文件(.h/.c/.cpp)
在工程设定中Project->Settings For box选中win32 debug
c/c++ ->Category选中General ->Preprocessor Definitions加入YYDEBUG
在工程Project设定Project->Settings For box中选中all
link -> Category选中General->Object/Library Modules中加入yld.lib
这里需要注意的是yld.lib为parser generator的DUBUG单线程版本,对于vc的控制台程序是可以的,如果使用了MFC或是Windows applications程序则需要对应下表追加
Library(DEBUG)
Run-time Library
Description
yld.lib
Debug Single-Threaded
单线程静态链接库(DEBUG版本)
ylmtd.lib
Debug Multithreaded
多线程静态链接库(DEBUG版本)
ylmtrd.lib
Debug Multithreaded DLL
多线程静态链接库当run time library 使用动态库(DEBUG版本)
ylmtrid.lib
Debug Multithreaded DLL
多线程动态链接库当run time library 使用动态库(DEBUG版本)
Library(RELEASE)
Run-time Library
Description
yl.lib
Single-Threaded
单线程静态链接库(RELEASE版本)
ylmt.lib
Multithreaded
多线程静态链接库(RELEASE版本)
ylmtr.lib
Multithreaded DLL
多线程静态链接库当run time library 使用动态库(RELEASE版本)
ylmtri.lib
Multithreaded DLL
多线程动态链接库当run time library 使用动态库(RELEASE版本)
如果使用了动态库版本需要在程序运行环境中追加DLL的地址
安装目录/PARSER GENERATOR 2/CPP/LIB/MSVC32
如果需要链接yacc或是lex的dll.在Preprocessor Definitions下加入YYDLL.
这样就可以使用vc6.0对lex生成文件进行编译了
3、PG2的默认输入为FILE *,怎样转化为char *?
PG2提供了两种方法,一是重定义yyinput,二是重定义yygetchar。实际使用中后者较方便,因为yyinput除了调用yygetchar之外还需要负责lineno变量的增加。
yygetchar通常这样定义
int yygetchar(void)
{
if (* inputstring=='/0')
{
return -1;
}
return (unsigned char) * inputstring++;
}
唯一要注意的就是读到char *的末尾时要返回一个-1代表EOF,使得yyinput停止。(感谢luocong的提醒)。
4、同理,yyoutput怎样从FILE *转化为char *?
直接对yytext操作既可,yytext就是char[]。
3,4说明转自
http://blog.csdn.net/tankaiha/archive/2005/12/13/551457.aspx
总体来说lex/yacc比起自己编写分析程序要快的多,但是不足点就是错误的调试会非常困难,需要把.lex/.l和.yacc/.y文件加入工程调试,每次变更都需要重新编译parser generator工程,然后再次粘贴,lex的语法熟练度可能决定了效率,新手基于模型为基础进行研究逐步认识parser generator是最佳的学习方法,
Lex的自带例程在/Parser Generator 2/Cpp/Examples 下,在这对其中的class(四则计算器)做下简单说明
lexer.l文件
%{ //%{ %}中的c代码将被复制到.cpp文件中
#include <stdlib.h>
#include <math.h>
#include <assert.h>
#include "parser.h"//自动生成头文件必须包含
%}
// include file
%include { //%include 将{}里的代码复制到对应的.h文件中,这里声明calc_parser和
class calc_parser; // symboltable是为在下面calc_lexer::create函数能够找到使用类型的提前
class symboltable; //声明
}
// lexical analyser name
%name calc_lexer //%name指定继承自lexer的类名{}中为类成员声明,类的声明将被解//释到.h文件中
// class definition
{
// Attributes
protected:
symboltable* m_symboltable; // the symbol table
// Operations
public:
int create(calc_parser* parser, symboltable* symboltable);
// attribute commands
double number() const;
symbol* id() const;
}
// constructor
{
// do nothing
}
// macros
exponent ([Ee][+-]?[0-9]+) //科学计算方定义为exponent
%%
%{
// extract yylval for use later on in actions
YYSTYPE& yylval = *(YYSTYPE*)yyparserptr->yylvalptr;//指定yylval值的类型
%}
// 对于数字的规则处理 |表示或,指|下一个算式使用的规则与第一个相同{}中是规则执行代码
[0-9]+"."[0-9]*{exponent}? |
"."[0-9]+{exponent}? |
[0-9]+{exponent}? { yylval.value = number(); return NUMBER; }
//对于3角函数的规则处理
"sin" { return SIN; }
"cos" { return COS; }
"tan" { return TAN; }
// 对于变量的规则处理
[a-zA-Z_][a-zA-Z0-9_]* { yylval.symbol = id(); return ID; }
//对于符号的规则处理
"=" { return '='; }
"+" { return '+'; }
"-" { return '-'; }
"*" { return '*'; }
"/" { return '/'; }
"(" { return '('; }
")" { return ')'; }
// 对于特殊符号的处理
[ /t] { /* do nothing */ }
/n { return '/n'; }
. { printf("invalid character '0x%02x'/n", (unsigned int)yytext[0]); }
%%
/
// 以下都是对成员函数的实现,将被复制到.cpp文件中
int calc_lexer::create(calc_parser* parser, symboltable* symboltable)
{
assert(parser != NULL);
assert(symboltable != NULL);
m_symboltable = symboltable;
return yycreate(parser);
}
/
// calc_lexer attribute commands
double calc_lexer::number() const
{
errno = 0; // clear error flag
char* endp;
double d = strtod(yytext, &endp);
if ((d == +HUGE_VAL || d == -HUGE_VAL) && errno == ERANGE) {
printf("number too big/n");
}
return d;
}
symbol* calc_lexer::id() const
{
symbol* p = m_symboltable->install(yytext);
return p;
}//lexer.l end
parser.y文件
%{ //%{ %}中的c代码将被复制到.cpp文件中
#include <math.h>
%}
// include file
%include { //%include 将{}里的代码复制到对应的.h文件中,这里声明symbol
// 是为在下面calc_parser:: assign函数能够找到使用类型的提前
//声明
// forward references
class symbol;
}
//%union表示使用联合体声明yytest的类型可能有2种,也就是lex返回的标记的值的类型(每//一个标记都会有一个值)
%union {
symbol* symbol;
double value;
}
// nonterminals
%type <value> expr //%type是声明变量expr为double类型,这里要注意的是<>声明的类型必//须是在%union中定义的变量
//这里要注意下越是后声明的优先级越高
%right '=' //%right声明=为右结合
%left '+', '-' //%left 声明+ -为左结合
%left '*', '/' //%left 声明* /为左结合
%right UMINUS //%right声明UMINUS为右结合,此优先级最高
%token <value> NUMBER //%token声明标记NUMBER和其类型,与%type注意点一样
%token <symbol> ID //声明标记ID的类型,与%type注意点一样
// keywords
%token SIN //声明标记SIN,因为本身的值没有用途所以不对其类型特殊声明
%token COS //声明标记COS,因为本身的值没有用途所以不对其类型特殊声明
%token TAN //声明标记TAN,因为本身的值没有用途所以不对其类型特殊声明
// include file
%include {
#include "symbol.h" //%{ %}中的c代码将被复制到.h文件中
#include "lexer.h"
}
// parser name
%name calc_parser //%name指定继承自lexer的类名{}中为类成员声明,类的声明将被解
//释到.h文件中
// class definition
{
// 类成员声明
protected:
symboltable m_symboltable; // the symbol table
calc_lexer m_lexer; // the lexical analyser
// Operations
public:
int create();
// attribute commands
double assign(symbol* id, double value);
double divide(double dividend, double divisor);
}
// constructor
{
// do nothing
}
%%
lines
: lines line
| /* empty */
;
line
: expr '/n' { printf("%g/n", (double)$1); }
| error '/n' { yyerrok(); }
;
//这里只对expr进行说明$$是冒号左边表达式的值 $1为右边第一个表达式的值
$2为右边第二个表达式的值 $3为右边第三个表达式的值,以此类推.
expr
: ID '=' expr { $$ = assign($1, $3); }//变量保存
| expr '+' expr { $$ = $1 + $3; }
| expr '-' expr { $$ = $1 - $3; }
| expr '*' expr { $$ = $1 * $3; }
| expr '/' expr { $$ = divide($1, $3); }//除法判断
| '(' expr ')' { $$ = $2; }
| '-' expr %prec UMINUS { $$ = -$2; }
// %prec说明'-' expr表达式的优先级和UMINUS一样.
| NUMBER { $$ = $1; }
| ID { $$ = $1->m_value; }
| SIN '(' expr ')' { $$ = sin($3); }
| COS '(' expr ')' { $$ = cos($3); }
| TAN '(' expr ')' { $$ = tan($3); }
;
%%
/
//以下都是对成员函数的实现,将被复制到.cpp文件中
int main(void)
{
int n = YYEXIT_FAILURE;
calc_parser parser;
if (parser.create()) {
n = parser.yyparse();
}
return n;
}
/
// calc_parser commands
int calc_parser::create()
{
if (!yycreate(&m_lexer)) {
return 0;
}
if (!m_lexer.create(this, &m_symboltable)) {
return 0;
}
return 1; // success
}
/
// calc_parser attribute commands
double calc_parser::assign(symbol* id, double value)
{
assert(id != NULL);
id->m_value = value;
return id->m_value;
}
double calc_parser::divide(double a, double b)
{
if (b == 0) {
printf("division by zero/n");
yythrowerror(); // causes a syntax error
return 0;
}
else {
return a / b;
}
}