Parser Generator와 Borland C++ 6.0을 이용해서 SQL 파서를 만들어 보자
준비물로는 Parser Generator 라는 Lex & Yacc 툴과 Borland C++ 6.0만 있으면 됩니다.
SQL 파서를 만들기 위해서 Delphi Lex & Yacc을 이용하다가 Delphi Lex & Yacc 에서 지원하는 것이
약해서 C++을 사용했던 것으로, Parser Generator 제품이 Borland C++ Builder을 지원하고 있지만
국내나 외국사이트를 보아도 자료를 찾기가 너무 힘들어 간단하게 제작한 소스를 올립니다.
Lex & Yacc과 C++ Builder에 대한 설명은 따로 하지 않겠습니다.
글로 써서 표현하는 능력이 떨어져 소스에 간단한 주석이 설명의 전부입니다.
▶ Parser Generator 다운로드 받기
http://www.bumblebeesoftware.com/
▶ Parser Generator 버전 정보
▶ Parser Generator - LibBuilder 메뉴로 Borland C++ Builder용 라이브러리 컴파일
메뉴) [Project] -> [LibBuilder...] 선택
Borland C++ Builder 체크 박스 선택
[LibBuilder 창]에서 [Properties...] 버튼 선택
Compiler Bin Directory,Compiler Include Directory, Compiler Library Directory 하나씩 선택하여
Borland C++ Builder의 경로를 지정
[LibBuilder 창]에서 [Directories...] 버튼 선택
Parser Generator 경로 지정
[LibBuilder 창]에서 [Build] 버튼 선택하면 아래와 같은 [Output 창]에서 컴파일 과정을 확인
▶ Parser Generator - Project 만들기
아래와 같은 과정을 거치면 sqllexer.l, sqlparser.y이 생성됨
메뉴) [Project] - [ParserWizard...] 선택
▶ sqllexer.l
%{
/****************************************************************************
sqllexer.l
ParserWizard generated Lex file.
****************************************************************************/
#include
#pragma hdrstop
#include
#include "sqlparser.h"
int lineno = 1;
%}
/////////////////////////////////////////////////////////////////////////////
// declarations section
%include {
#include
#pragma hdrstop
#include
}
// lexical analyser name
%name sqllexer
// class definition
{
// place any extra class members here
Classes::TStrings* FLines;
void SetLog(Classes::TStrings* ALines);
}
// constructor
{
// place any extra initialisation code here
}
// destructor
{
// place any extra cleanup code here
}
// place any declarations here
%%
/////////////////////////////////////////////////////////////////////////////
// rules section
%{
// extract yylval for use later on in actions
YYSTYPE YYFAR& yylval = *(YYSTYPE YYFAR*)yyparserptr->yylvalptr;
%}
// place your Lex rules here
/* literal keyword tokens */
ADA { return ADA; }
ALL { return ALL; }
AND { return AND; }
AVG { return AMMSC; }
MIN { return AMMSC; }
MAX { return AMMSC; }
SUM { return AMMSC; }
COUNT { return AMMSC; }
ANY { return ANY; }
AS { return AS; }
ASC { return ASC; }
AUTHORIZATION { return AUTHORIZATION; }
BETWEEN { return BETWEEN; }
BY { return BY; }
C { return C; }
CHAR(ACTER)? { return CHARACTER; }
CHECK { return CHECK; }
CLOSE { return CLOSE; }
COBOL { return COBOL; }
COMMIT { return COMMIT; }
CONTINUE { return CONTINUE; }
CREATE { return CREATE; }
CURRENT { return CURRENT; }
CURSOR { return CURSOR; }
DECIMAL { return DECIMAL; }
DECLARE { return DECLARE; }
DEFAULT { return DEFAULT; }
DELETE { return DELETE; }
DESC { return DESC; }
DISTINCT { return DISTINCT; }
DOUBLE { return DOUBLE; }
ESCAPE { return ESCAPE; }
EXISTS { return EXISTS; }
FETCH { return FETCH; }
FLOAT { return FLOAT; }
FOR { return FOR; }
FOREIGN { return FOREIGN; }
FORTRAN { return FORTRAN; }
FOUND { return FOUND; }
FROM { printf("find from\n"); this->FLines->Add("select"); return FROM; }
GO[ \t]*TO { return GOTO; }
GRANT { return GRANT; }
GROUP { return GROUP; }
HAVING { return HAVING; }
IN { return IN; }
INDICATOR { return INDICATOR; }
INSERT { return INSERT; }
INT(EGER)? { return INTEGER; }
INTO { return INTO; }
IS { return IS; }
KEY { return KEY; }
LANGUAGE { return LANGUAGE; }
LIKE { return LIKE; }
MODULE { return MODULE; }
NOT { return NOT; }
NULL { return NULLX; }
NUMERIC { return NUMERIC; }
OF { return OF; }
ON { return ON; }
OPEN { return OPEN; }
OPTION { return OPTION; }
OR { return OR; }
ORDER { return ORDER; }
PASCAL { return PASCAL; }
PLI { return PLI; }
PRECISION { return PRECISION; }
PRIMARY { return PRIMARY; }
PRIVILEGES { return PRIVILEGES; }
PROCEDURE { return PROCEDURE; }
PUBLIC { return PUBLIC; }
REAL { return REAL; }
REFERENCES { return REFERENCES; }
ROLLBACK { return ROLLBACK; }
SCHEMA { return SCHEMA; }
SELECT { printf("find select\n"); this->FLines->Add("select"); return SELECT; }
SET { return SET; }
SMALLINT { return SMALLINT; }
SOME { return SOME; }
SQLCODE { return SQLCODE; }
TABLE { return TABLE; }
TO { return TO; }
UNION { return UNION; }
UNIQUE { return UNIQUE; }
UPDATE { return UPDATE; }
USER { return USER; }
VALUES { return VALUES; }
VIEW { return VIEW; }
WHENEVER { return WHENEVER; }
WHERE { return WHERE; }
WITH { return WITH; }
WORK { return WORK; }
/* punctuation */
"=" |
"<>" |
"<" |
">" |
"<=" |
">=" { return COMPARISON; }
[-+*/:(),.;] { return yytext[0]; }
/* names */
[A-Za-z][A-Za-z0-9_]* {
yylval.strval = strdup(yytext);
return NAME;
}
/* numbers */
[0-9]+ |
[0-9]+"."[0-9]* |
"."[0-9]* { return INTNUM; }
[0-9]+[eE][+-]?[0-9]+ |
[0-9]+"."[0-9]*[eE][+-]?[0-9]+ |
"."[0-9]*[eE][+-]?[0-9]+ { return APPROXNUM; }
/* strings */
'[^'\n]*' {
int c = input();
unput(c); /* just peeking */
if(c != '\'') {
return STRING;
} else
yymore();
}
'[^'\n]*$ { yyerror("Unterminated string"); }
\n {
lineno++;
// this->FLines->Add("aaa");
}
[ \t\r]+ ; /* white space */
"--".*$ ; /* comment */
%%
/////////////////////////////////////////////////////////////////////////////
// programs section
void sqllexer::SetLog(Classes::TStrings* ALines) {
this->FLines = ALines;
}
▶ sqlparser.y
%{
/****************************************************************************
sqlparser.y
ParserWizard generated YACC file.
****************************************************************************/
#include
#pragma hdrstop
#include
#include "sqllexer.h"
%}
/////////////////////////////////////////////////////////////////////////////
// declarations section
/* symbolic tokens */
%union {
int intval;
double floatval;
char *strval;
int subtok;
}
%token NAME
%token STRING
%token INTNUM APPROXNUM
/* operators */
%left OR
%left AND
%left NOT
%left COMPARISON /* = <> < > <= >= */
%left '+' '-'
%left '*' '/'
%nonassoc UMINUS
%include {
#include
#pragma hdrstop
#include
}
// parser name
%name sqlparser
// class definition
{
// place any extra class members here
Classes::TStrings* FLines;
void SetLog(Classes::TStrings* ALines);
}
// constructor
{
// place any extra initialisation code here
}
// destructor
{
// place any extra cleanup code here
}
// attribute type
%include {
#ifndef YYSTYPE
#define YYSTYPE int
#endif
}
// place any declarations here
/* literal keyword tokens */
%token ALL AMMSC ANY AS ASC AUTHORIZATION BETWEEN BY
%token CHARACTER CHECK CLOSE COMMIT CONTINUE CREATE CURRENT
%token CURSOR DECIMAL DECLARE DEFAULT DELETE DESC DISTINCT DOUBLE
%token ESCAPE EXISTS FETCH FLOAT FOR FOREIGN FOUND FROM GOTO
%token GRANT GROUP HAVING IN INDICATOR INSERT INTEGER INTO
%token IS KEY LANGUAGE LIKE MODULE NULLX NUMERIC OF ON
%token OPEN OPTION ORDER PRECISION PRIMARY PRIVILEGES PROCEDURE
%token PUBLIC REAL REFERENCES ROLLBACK SCHEMA SELECT SET
%token SMALLINT SOME SQLCODE SQLERROR TABLE TO UNION
%token UNIQUE UPDATE USER VALUES VIEW WHENEVER WHERE WITH WORK
%token COBOL FORTRAN PASCAL PLI C ADA
%%
/////////////////////////////////////////////////////////////////////////////
// rules section
// place your YACC rules here (there must be at least one)
// place your YACC rules here (there must be at least one)
query_spec:
SELECT selection table_exp ';'
;
table_exp:
from_clause
;
from_clause:
FROM table_ref_commalist
;
table_ref_commalist:
table_ref
| table_ref_commalist ',' table_ref
;
table_ref:
table { this->FLines->Add($1.strval); }
| table range_variable
;
range_variable: NAME
;
table:
NAME
| NAME '.' NAME
;
selection:
scalar_exp_commalist
| '*'
;
scalar_exp_commalist:
scalar_exp
| scalar_exp_commalist ',' scalar_exp
;
scalar_exp:
column_ref {
this->FLines->Add($1.strval);
}
;
column_ref:
NAME
| NAME '.' NAME /* needs semantics */
| NAME '.' NAME '.' NAME
;
%%
/////////////////////////////////////////////////////////////////////////////
// programs section
/*
int main(void)
{
int n = 1;
sqllexer lexer;
sqlparser parser;
if (parser.yycreate(&lexer)) {
if (lexer.yycreate(&parser)) {
n = parser.yyparse();
}
}
return n;
}
*/
void sqlparser::SetLog(Classes::TStrings* ALines) {
this->FLines = ALines;
}
▶ Lex & Yacc 파일 Build 하기
메뉴) [Project] -> [Build] 선택
Build 하게 되면
sqllexer.l 파일은 sqllexer.h, sqllexer.cpp 파일이 생성되고,,
sqlparser.y 파일은 sqlparser.h, sqlparser.cpp 파일이 생성됨
▶ Borland C++ Builder
Parser Generator 의 Include, Source와 위에서 컴파일한 Library 파일을 Borland C++ Builder의
디렉토리 설정에서 지정하고,
Project을 생성하여, Parser Generator에서 생성된 파일 sqllexer.l, sqllexer.h, sqllexer.cpp,
sqlparser.y, sqlparser.h, sqlparser.cpp을 프로젝트에 추가
버튼과 메모 컴포넌트를 추가하여 아래와 같이 코딩
▶ Unit1.cpp 파일
//---------------------------------------------------------------------------
#include
#pragma hdrstop
#include "Unit1.h"
#include
#include
#include "sqllexer.h"
#include "sqlparser.h"
//---------------------------------------------------------------------------
#pragma package(smart_init)
#pragma resource "*.dfm"
TForm1 *Form1;
//---------------------------------------------------------------------------
__fastcall TForm1::TForm1(TComponent* Owner)
: TForm(Owner)
{
}
//---------------------------------------------------------------------------
void __fastcall TForm1::Button1Click(TObject *Sender)
{
ifstream testfile ("D:\\source_sqlc\\sql_2\\TestSQL.txt");
ofstream testfile2 ("D:\\source_sqlc\\sql_2\\TestSQL.out");
ofstream testfile3 ("D:\\source_sqlc\\sql_2\\TestSQL.err");
int n = 1;
sqllexer lexer;
sqlparser parser;
if (parser.yycreate(&lexer)) {
if (lexer.yycreate(&parser)) {
lexer.SetLog(Memo1->Lines);
parser.SetLog(Memo1->Lines);
lexer.yyin = &testfile;
lexer.yyout = &testfile2;
lexer.yyerr = &testfile3;
n = parser.yyparse();
}
}
// return n;
}
//---------------------------------------------------------------------------
void __fastcall TForm1::Button2Click(TObject *Sender)
{
Memo1->Lines->Add("a");
}
//---------------------------------------------------------------------------
▶ TestSQL.txt 내용
SELECT aa1, bb1
FROM bbaaaaaa;
▶ 실행 결과