* 작성되는글은 Holub on Pattern 을 보고 정리한것입니다.


먼저 BNF



statement  ::= INSERT INTO INDENTIFIER [LP idList RP] VALUES LP exprList RP 

| CREATE DATABASE IDENTIFIER

| CREATE TABLE IDENTIFIER LP declarations RP

| DROP TABLE IDENTIFIER

| BEGIN | WORK | TRAN[SACTION]]

| COMMIT | WORK | TRAN[SACTION]]

| ROLLBACK [WORK | TRAN[SACTION]]

| DUMP

| USE DATABASE IDENTIFIER

| UPDATE IDENTIFIER SET IDENTIFIER EQUAL expr WHERE expr

| DELETE FROM IDENTIFIER WHERE expr

| SELECT [INTO identifier idList FROM idList [WHERE expr]


 

idList       ::= IDENTIFIER idList' | STAR

idList'       ::= COMMA IDENTIFIER idList' |  ε


declarations  ::= IDENTIFIER [type] [NOT [NULL]] declarations'

declarations'  ::= COMMA IDENTIFIER [type] declarations' 

  | COMMA PRIMARY KEY LP IDENTIFER RP  |  ε


type        ::= INTEGER [ LP expr RP ]

|  CHAR [ LP expr RP ]

| NUMERIC [ LP expr COMMA expr RP ]

| DATE


exprList     ::= expr exprList'

exprList'    ::= COMMA expr exprList'  |  ε


expr        ::= andExpr expr'

expr'       ::= OR andExpr expr'


andExpr    ::= relationalExpr andExpr'

andExpr'   ::= AND relationalExpr andExpr''


relationalExpr    ::= additiveExpr relationalExpr'

relationalExpr'    ::= RELOP additiveExpr relationalExpr'

|   EQUAL additiveExpr relationalExpr'

|  LIKE    additiveExpr relationalExpr'

|  ε



addtiveExpr     ::=  multiplicativeExpr additiveExpr'

additiveexpr'    ::= ADDITIVE multiplictiveExpr additiveExpr'


multiplicativExpr    ::= term multiplicativeExpr'

multiplicativeExpr   ::= STAR term multiplicativeExpr'

| SLASH term multiplicativeExpr'

|  ε


term             ::=  NOT factor

|     LP expr RP

|     factor


factor            ::= compoundId | STRING | NUMBER | NULL


compoundId     ::= IDENTIFIER compoundId'

compoundId'    ::= DOT IDENTIFIER

|  ε




위의 BNF 를 기준으로  이터레이터패턴을 적용하여 클래스를 만든후에 






SELECT     first, last 

FROM       people, zip 

WHERE     people.last = 'Flintstone' 

AND    people.first = 'Fred' 

OR     people.zip > (94700 + zip.margin) 


이 문장을 파싱(Recursive-descent 법으로) 해서 구문트리(추상문법트리) 를 만들어보면 아래와 같다. 



좀 구체적인 물리적 클래스에 의한 추상 문법 트리는 아래와 같다.




* 위에 그림에서 AND, OR 로 연결된 부분은  LogicalExpression 이다. (RelationalExpression 이 아님 ) 


결국 순서는 BNF 구성 ->  클래스 설계 -> 실제 구문 파싱 -> 추상문법트리 구성 -> 실제 구문 평가 



실제 작동하는 소스를 보면 


전체적 순서는 


else if( in.matchAdvance(SELECT) != null )      // SELECT 로 시작될때

{ List columns = idList();            // 컬럼들을 리스트로 얻어옴.


String into = null;

if( in.matchAdvance(INTO) != null )    // INTO 가 있다면 

into = in.required(IDENTIFIER);


in.required( FROM );                         // FROM 이 있어야한다.

List requestedTableNames = idList(); // 조인될 테이블들 리스트로 얻어옴.


  //파싱후 추상구문트리 완성

Expression where = (in.matchAdvance(WHERE) == null)  ? null : expr();

 

        // 추상구문트리를 이용하여 실제 테이블들을 평가해서 새 테이블을 만듬.

Table result = doSelect(columns, into, requestedTableNames, where );

return result;

}



파싱 


private Expression expr() throws ParseFailure

{ Expression left = andExpr();

while( in.matchAdvance(OR) != null )

left = new LogicalExpression( left, OR, andExpr());

return left;

}


private Expression andExpr() throws ParseFailure

{ Expression left = relationalExpr();

while( in.matchAdvance(AND) != null )

left = new LogicalExpression( left, AND, relationalExpr() );

return left;

}


......



평가


private Table doSelect( List columns, String into,

List requestedTableNames,

final Expression where )

throws ParseFailure

{


Iterator tableNames = requestedTableNames.iterator();


assert tableNames.hasNext() : "No tables to use in select!" ;


// The primary table is the first one listed in the

// FROM clause. The participantsInJoin are the other

// tables listed in the FROM clause. We're passed in the

// table names; use these names to get the actual Table

// objects.


Table primary = (Table) tables.get( (String) tableNames.next() );


List participantsInJoin = new ArrayList();

while( tableNames.hasNext() )

{ String participant = (String) tableNames.next();

participantsInJoin.add( tables.get(participant) );

}


// Now do the select operation. First create a Strategy

// object that picks the correct rows, then pass that

// object through to the primary table's select() method.


Selector selector = (where == null) ? Selector.ALL : //{=Database.selector}

new Selector.Adapter()

{ public boolean approve(Cursor[] tables)

{ try

{

Value result = where.evaluate(tables);


verify( result instanceof BooleanValue,

"WHERE clause must yield boolean result" );

return ((BooleanValue)result).value();

}

catch( ParseFailure e )

{ throw new ThrowableContainer(e);

}

}

};


try

{ Table result = primary.select(selector, columns, participantsInJoin);


// If this is a "SELECT INTO <table>" request, remove the 

// returned table from the UnmodifiableTable wrapper, give

// it a name, and put it into the tables Map.


if( into != null )

{ result = ((UnmodifiableTable)result).extract();

result.rename(into);

tables.put( into, result );

}

return result;

}

catch( ThrowableContainer container )

{ throw (ParseFailure) container.contents();

}

}


'인터프리터' 카테고리의 다른 글

Scala 언어를 이용한 미니언어 만들기  (0) 2015.06.06
Recursive Descent vs Lex/Parse?  (0) 2015.05.19
Recursive Descent Parsing  (0) 2015.05.19


GOF 의 디자인패턴중 하나인 인터프리터 패턴에 대해서 살펴보도록 하자. 


인터프리터 패턴은 모든 종류의 GoF 패턴들 중에 가장 어렵다. 물론 인터프리터 자체를 만드는게 어렵


기때문에 덩달아  어렵다는 이야기이고, 모습 자체는 여느 디자인패턴과 비슷하다. 


원래  디자인패턴이라는건 사실 모양은 다 비슷비슷하다.  그게 쓰여지는 의도에 따라서 패턴의 구분이 


이루어진다. 인터프리터 패턴은 느낌 그대로  어떤 해석기, 즉 "간이언어" 를 만들기 위한 패턴이다.  


예를들어 클라이언트-서버 프로그램을 만들때 ,  프로토콜로 "사람이 자연스럽게 읽을수 있는  문법형태"

  

로 하자!! 라고 정할수있는데, 그때 사용하면  되겠다.  프로토콜 예로는 "Select  * From  Table"  이렇게~



자 이 글에서는 


"program repeat 3 go right end  end"  라는 문장을 해석해서 실행하는 프로그램을 예로 들어 설명해보겠다. 


"자바언어로 배우는 디자인 패턴 입문" 이라는 책을 참고했다. 



일단 저 문장에 대해서 BNF 라고 하는 언어의 문법을 표기할때 사용되는 표기법으로 분석해보자.


<program> ::= program <command list>

<command list> ::= <command>* end

<command> ::= <repeat command> | <primitive command>

<repeat command> ::= repeat <number> <command list>

<primitive command>::= go | right | left 


사실 저 BNF 를 만드는게 가장 어렵다고 볼수있다.


만약 SQL 을 저런식으로 만든다고 상상해보자.


statement  ::= INSERT INTO INDENTIFIER [LP idList RP] VALUES LP exprList RP 

| CREATE DATABASE IDENTIFIER

| CREATE TABLE IDENTIFIER LP declarations RP

| DROP TABLE IDENTIFIER

| BEGIN | WORK | TRAN[SACTION]]

| COMMIT | WORK | TRAN[SACTION]]

| ROLLBACK [WORK | TRAN[SACTION]]

| DUMP

| USE DATABASE IDENTIFIER

| UPDATE IDENTIFIER SET IDENTIFIER EQUAL expr WHERE expr

| DELETE FROM IDENTIFIER WHERE expr

| SELECT [INTO identifier idList FROM idList [WHERE expr]


...

등등 무수히 많은 정의가 추가될것이다.



돌아가서 첫번째 BNF 를  토대로 클래스 다이어그램을 만들어보자. 






위 클래스 다이어그램에 


"program repeat 3 go right end  end" 를 파싱해서 객체트리를 만들어보자. 이 객체트리를 구문트리


라고 한다. 객체트리를 만드는 방법은 문장을 토큰화하여  한 단어씩 분석하면서 진행된다.  처음에 


program 객체를 만들고,  해당 객체가  가지고 있을수있는 CommandListNode 객체를  만들고


CommandListNode 는  CommandNode 객체(들)를 만들고 (이때 Repeat / Primitive 선택) 이런식으로~


Primitive 는 go 라는 명령어를 가지고있게되고,나중에 순회하면서 go 에 해당하는 행동을 하게함.





만들어진 구문 트리 시작 객체의 Execute 함수를 실행하면  구문트리를 순회하게 된다. 


자동차를 저 문장을 가지고 실행하게된다면  앞으로(go) 갔다가 오른쪽(right)으로 도는 행동을 


3번 연속하게된다. 만약 SQL 문으로 저런 구문트리를 만들었다고 가정하면 , 2차배열의 테이블을 


구문트리 객체에 파라미터로  넘기면 , 구문트리를 돌면서 해당 객체의 수행방법에 따라서


해당 테이블에서 특정  데이터만 뽑아낼수있을것이다.

+ Recent posts