Parser Generator的使用說明
最近1個星期,大致學習了一下lex,雖然在windows系統上它並沒有我所期望的強大,在調試和編寫代碼都遇到了不少困難,但是總體來說Parser Generator還是讓我體會到了lex編程的快捷, 爲了自己加深印象把一些參考資料和認識總結了一下.
1. parser generator是什麼?
它是一款在windows下編寫lex/yacc程序的工具.可以到
http://www.bumblebeesoftware.com/downloads.htm
下載
2. 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;
}
}
更多詳細參考可以查看Parser Generator Help幫助文檔
以上都是作者的辛苦勞動,轉載請註明出處.