MPML (Midnight Peach Macro Language)

Midnight Peach 2008. 10. 26. 13:44

[주의]
이 글은 MP의 현재 버전 (3.X)에 맞지 않습니다.

단순 참고용으로만 활용하십시오

MP의 템플릿은 고유한 문법을 가지고 있습니다.

이 문법으로 기술된 언어를 Midnight Peach Macro Language, 줄여서 MPML 라고 합니다.

이번 포스트에서는 이 MPML의 문법에 대해서 살펴보도록 하겠습니다.

 

이전 포스트인 MP의 구성에서 말씀드렸듯이, MP는 패키지 DBML에 정의되어 있는 엔터티 클래스를 원본으로 하여 (패키지에 포함된) 템플릿을 치환하는 방식으로 코드를 생성합니다.

이때 템플릿에는 엔터티의 클래스를 어떻게 치환하여야 하는지에 대한 정보가 기술되어 있습니다. 이 정보가 일정한 규칙(문법)을 따라야만 치환이 정상적으로 이루어집니다.

바로 이 규칙(문법)을 정의해 놓은 것이 MPML입니다.

 

만일 사용자가 MP에 내장된 Default 패키지를 수정하여 사용하여야 한다면, 패키지에 포함된 템플릿을 수정하여야 합니다.

템플릿을 수정하기 위해서는 MPML의 문법에 대해서 알 필요가 있습니다.

결과적으로 MPML을 알아야 하는 이유는 패키지를 커스터마이즈 하여 사용자의 필요에 맞는 코드를 생성하기 위해서라고 하겠습니다.

 

MPML은 매크로를 정의하는 (언어라고 부르기도 힘들 정도록) 대단히 단순한 언어입니다. 따라서 문법도 무척 간략합니다.

먼저 MPML의  EBNF는 다음과 같습니다.

(참고로 EBNF, 즉 확장된 바커스-노어 폼은 언어의 사양을 정의하는 메타 언어입니다. EBNF에 대한 설명은 이 포스트의 범위를 벗어나므로 위키피디아의 항목 등을 참고하시기 바랍니다.)

 

template = {database | table loop | table | column loop | comment | variable | character} ;

database = "<#D " , "CamelName" | "PascalName" | "DataContext" , " D#>" ;

table loop = "<#A " , table loop type , ":", {loop seperator} , ":", {character | table} , " A#>" ;

table loop type = "AL" | "TB" | "VW" ;

loop seperator = "[N]" | "[T]" | character ;

table = "<#T " , "CamelName" | "PascalName" | "DbName" | "Schema" | "BeginCommentIfView" | "EndCommentIfView" , " T#>" ;

column loop = "<#O " , column loop type , ":" , {loop seperator} , ":", {character | column} , " O#>" ;

column =  "<#C " , "CamelName" | "PascalName" | "DbName" | "Type" | "DbType" | "BeginCommentIfReadOnly" | "EndCommentIfReadOnly",  " C#>" ;

column loop type = "AL" | "PK" | "NP" | "IN" | "NI" | "RO" | "NR" | "NU" | "NN";

variable = "<#V ", {character}, " V#>";

comment = "<#- ", {character}, " -#>";

character = [a-z] | [A-Z] | [0-9] | white space ;

 

위  EBNF에 대해서 실제 템플릿 코드와 함께 하나씩 살펴봅시다.

 

1. template = {database | table loop | table | column loop | variable | comment | character} ;

EBNF의 정의에 의하면 이 라인은, '템플릿은 database, table loop, table, column loop, variable, comment, character 요소의 반복으로 이루어져 있다'는 의미가 되겠습니다. 따라서 위 EBNF의 두 번째 라인부터는 각 요소들에 대한 정의가 이어집니다.

 

2. database = "<#D " , "CamelName" | "PascalName" | "DataContext" , " D#>" ;

database요소에 대한 정의입니다. "|" 는 "또는"의 의미이므로,  database는 "<#D "로 시작하고, "CamelName" 이나 "PascalName" 이나 "DataContext"가 이어지고, " D#>"로 끝난다는 말이 되겠네요.

그리고 이때 "CamelName" 이나 "PascalName" 이나 "DataContext"는 각각 데이터베이스의 소문자 이름, 대문자 이름, DataContext 클래스 이름으로 치환되도록 미리 정해진 속성입니다.

 

예를 들어 아래와 같은 템플릿을 만나면,

internal static <#D DataContext D#> CreateDataContext()
{
    return CreateDataContext(false, true);
}

MP는 <#D DataContext D#>를 이렇게 해석합니다.

'<#D 로 시작하고 D#>로 끝나니 이것은 (DBML) 데이터베이스에 대한 매크로이구나. 또한 DataContext 라고 했으니 이 부분을 DBML에 정의되어 있는 데이터베이스의 DataContext 객체 이름으로 치환해야겠구나'

따라서 다음과 같은 코드가 생성됩니다. (DBML의 DataContext가 MemopadDataContext라고 가정합니다.)

internal static MemopadDataContext CreateDataContext()
{

    return CreateDataContext(false, true);
}

 

database요소의 가능한 값은 다음과 같습니다.

매크로 설명 (샘플 프로젝트의 경우)
PascalName 대문자로 시작하는 이름 <#D PascalName D#> ==> Memopad
CamelName 소문자로 시작하는 이름 <#D CamelName D#> ==> memopad
DataContext DataContext의 형식 이름 <#D DataContext D#> ==> MemopadDataContext

 

3. table loop = "<#A " , table loop type , ":", {loop seperator} , ":", {character | table} , " A#>" ;

table loop 요소는 조건에 해당하는 엔터티(테이블 혹은 뷰)를 반복하며 코드를 생성하는 요소입니다.

"<#A"와 "#A>" 태그로 시작하고 끝나는데, 태그의 내부는 ":" 으로 구분된 세 부분으로 이루어져 있습니다.

이는 각각 반복할 엔터티의 종류, 각 반복 시의 구분자, 반복 내용인데, EBNF 문법에서 알 수 있듯이, 첫번째 부분은 한 번만 나오는 반면에 두번째와 세번째 부분은 0회 이상 반복될 수 있습니다.

 

4. table loop type = "AL" | "TB" | "VW" ;

table loop type 요소는 반복할 엔터티의 종류를 지정합니다.

각 매크로의 의미는 다음과 같습니다.

매크로 설명 (샘플 프로젝트의 경우)
AL 모든 엔터티

<#A AL:: A#> ==>

Contact, Member, Memo, Reading, ReadingView를 반복

TB 테이블인 엔터티만

<#A TB:: A#> ==>

Contact, Member, Memo, Reading를 반복

VW 뷰인 엔터티만

<#A VW:: A#> ==>

ReadingView를 반복

 

5. loop seperator = "[N]" | "[T]" | character ;

엔터티(테이블 혹은 뷰)와 엔터티의 속성(컬럼)이 반복될 때 각 반복 사이에 삽입될 문자열을 지정합니다. 반복 사이이기 때문에 마지막 반복 후에는 문자열이 삽입되지 않습니다.

가능한 값의 목록은 다음과 같습니다.

매크로 설명 (샘플 프로젝트의 경우)
[N] 줄바꿈(\r\n)을 반복 사이에 삽입합니다.

<#A TB:[N]:<#T PascalName T#> A#> ==>

Contact

Member

Memo

Reading

[T] 탭(\t)을 반복 사이에 삽입합니다.

<#A TB:[T]:<#T PascalName T#> A#> ==>

Contact    Member    Memo    Reading

character 사용자가 지정한 문자열을 반복 사이에 삽입합니다. 아래 character 요소를 참조하십시오.

<#A TB:, :<#T PascalName T#> A#> ==>

Contact, Member, Memo, Reading

 

6. table = "<#T " , "PascalName" | "CamelName" | "DbName" | "Schema" | "BeginCommentIfView" | "EndCommentIfView" , " T#>" ;

table 요소는 엔터티의 여러가지 속성에 대한 매크로를 가지고 있습니다.

각 매크로의 의미는 다음과 같습니다.

매크로 설명 (샘플 프로젝트의 Contact 테이블의 경우)
PascalName 엔터티의 대문자로 시작하는 이름 <#T PascalName T#> ==> Contact
CamelName 엔터티의 소문자로 시작하는 이름 <#T CamelName T#> ==> contact
DbName SQL 서버 테이블(뷰)의 이름
SQL 서버 테이블(뷰)의 이름과 엔터티의 이름은 다를 수 있습니다.
<#T DbName T#> ==> Contact
(테이블과 엔터티의 이름이 동일)
Schema SQL 서버 테이블(뷰)의 스키마.
일반적으로는 "dbo"이며, SQL 서버 2005 이상에서만 지원.
<#T Schema T#> ==> dbo
BeginCommentIfView 엔터티가 뷰인 경우 "/*"를 반환 <#T BeginCommentIfView T#> ==>
(Contact는 테이블이기 때문에 아무 것도 생성되지 않음)
<#T BeginCommentIfView T#> ==> /*
(엔터티가 Contact가 아니라 ReadingView인 경우)
EndCommentIfView 엔터티가 뷰인 경우 "*/"를 반환 <#T EndCommentIfView T#> ==>
(Contact는 테이블이기 때문에 아무 것도 생성되지 않음)
<#T EndCommentIfView T#> ==> */
(엔터티가 Contact가 아니라 ReadingView인 경우)

 

여기까지의 요소들이 합쳐진 템플릿의 예를 볼까요?

<#A AL:[N]:
public static <#T PascalName T#>Manager <#T CamelName T#> { get; private set; }
A#>

1)모든(AL) 엔터티에 대해 2)각 반복에 있어 줄바꿈문자([N])를 구분자로 하여 3)"public static <#T PascalName T#>Manager <#T PascalName T#> { get; private set; }"라는 문자열을 반복하라는 의미가 됩니다.

또한, 반복되는 "public static <#T PascalName T#>Manager <#T CamelName T#> { get; private set; }" 문자열에 있어서도 "<#T PascalName T#>"와 "<#T CamelName T#>"를 각각 엔터티의 파스칼 이름과 카멜 이름으로 치환을 하게 됩니다.

결과적으로 이 템플릿에 샘플 프로젝트의 DBML을 대입하면 아래와 같은 코드가 생성됩니다.

public static ContactManager contact { get; private set; }
public static MemberManager member { get; private set; }
public static MemoManager memo { get; private set; }
public static ReadingManager reading { get; private set; }
public static ReadingViewManager readingView { get; private set; }

 

7. column loop = "<#O " , column loop type , ":" , {loop seperator} , ":", {character | column} , " O#>" ;

column loop 요소는 table loop 요소가 엔터티에 대한 반복을 나타내는 것 처럼 엔터티의 속성에 대한 반복을 나타냅니다.

table loop 요소와 마찬가지로 ":" 으로 구분된 세 부분으로 이루어져 있는데, 역시 반복할 엔터티 속성의 종류, 각 반복 시의 구분자, 반복 내용에 해당됩니다.

이 중 반복 시의 구분자는 table loop와 동일하며, 반복 내용은 table 대신 column 요소가 사용된다는 점만 제외하면 동일합니다.

 

8. column loop type = "AL" | "PK" | "NP" | "IN" | "NI" | "RO" | "NR" | "NU" | "NN";

column loop type 요소는 반복할 엔터티 속성의 종류를 지정하는 것입니다.

각 매크로의 의미는 다음과 같습니다.

매크로 설명
AL ALl. 모든 컬럼  
PK Primary Key. 기본키인 컬럼  
NP Not Primary key. 기본키가 아닌 컬럼  
IN INcremental. 자동증가 컬럼  
NI Not Incremental. 자동증가가 아닌 컬럼  
RO Read Only. 읽기 전용 컬럼  
NR Not Read only. 읽기 전용이 아닌 컬럼  
NU NUllable. 널을 허용하는 컬럼  
NN Not Nullable. 널을 허용하지 않는 컬럼  

 

9. column =  "<#C " , "PascalName" | "CamelName" | "DbName" | "Type" | "DbType" | "BeginCommentIfReadOnly" | "EndCommentIfReadOnly",  " C#>" ;

엔터티의 속성에 대한 정보를 나타내는 요소입니다.

매크로 설명 (샘플 프로젝트의 Contact.Name 컬럼의 경우)
PascalName 엔터티 속성의 대문자로 시작하는 이름 <#C PascalName C#> ==> Name
CamelName 엔터티 속성의 소문자로 시작하는 이름 <#C CamelName C#> ==> name
DbName SQL 서버 테이블(뷰)의 컬럼 이름
SQL 서버 테이블(뷰)의 컬럼 이름과 엔터티의 속성 이름은 다를 수 있습니다.
<#C DbName C#> ==> Name
(테이블의 컬럼과 엔터티의 속성 이름이 동일)
Type 엔터티 속성의 C# 형식 <#C Type C#> ==> string
DbType SQL 서버 테이블(뷰) 컬럼의 형식 <#C DbType C#> ==> nvarchar(50)
BeginCommentIfReadOnly 읽기 전용 컬럼인 경우 "/*"를 반환 <#C BeginCommentIfReadOnly C#> ==>
(Contact.Name은 읽기 전용 컬럼이 아니기 때문에 아무 것도 생성되지 않음)
EndCommentIfReadOnly 읽기 전용 컬럼인 경우 "*/"를 반환 <#C EndCommentIfReadOnly C#> ==>
(Contact.Name은 읽기 전용 컬럼이 아니기 때문에 아무 것도 생성되지 않음)

 

10. variable = "<#V ", {character}, " V#>";

variable 요소는 런타임에 사용자가 입력한 값으로 치환되는 매크로 입니다.

즉 위의 모든 요소는 DBML 파일에 정의된 엔터티에 의해 치환이 되는 반면에, variable 요소는 사용자가 지정한 값에 의해 치환이 됩니다.

따라서 variable요소에는 미리 정해진 매크로가 없습니다.

사용자가 런타임에 입력하여야 할 변수의 이름은 패키지 마다 상이한데, 이는 패키지 정의 파일(pds)의 <Variable> 요소에 정의되게 됩니다.

 

예를 들어 기본 패키지인 Default의 경우 패키지 정의 파일에는 아래와 같은 코드가 있습니다.

<Variable>
  <Name>Namespace</Name>
  <Value>Memopad.Biz</Value>
  <Description>생성될 코드의 네임스페이스</Description>
</Variable>
<Variable>
  <Name>ConnectionString</Name>
  <Value>ConfigurationManager.ConnectionStrings[0].ConnectionString</Value>
  <Description>연결 문자열</Description>
</Variable>
<Variable>
  <Name>CollectionType</Name>
  <Value>List</Value>
  <Description>Get이 반환할 제네릭 컬렉션 형식. Add(T item) 메서드를 가지고 있어야 함.</Description>
</Variable>

이는 UI 상으로는 아래 그림과 같이 표시됩니다.

빨간 줄은 사용자가 입력하여야 할 변수의 이름이고, 파란 줄은 실제 입력하는 값입니다.

위 그림과 같이 Namespace 라는 변수에 Memopad.Biz 라는 값을 입력하였다면,

템플릿에서 <#V Namespace V#> 라는 매크로는 Memopad.Biz로 치환되는 것입니다.

 

10. comment = "<#- ", {character}, " -#>";

comment 요소는 코드의 생성과는 상관 없이 템플릿에 주석을 달기 위해 사용하는 요소입니다.

반복하지만 comment 요소는 코드로 치환되지 않습니다. 단지 템플릿 내의 주석을 달기 위한 용도로만 사용됩니다.

 

11. character = [a-z] | [A-Z] | [0-9] | white space ;

MPML의 정의에 의하면 character 요소는 '영어 대소문자, 숫자, 화이트 스페이스의 0번 이상 반복' 으로 정의되어 있긴 하지만, 그냥 일반적인 문자열로 생각하셔도 무방합니다.

EBNF의 규칙에 의하면 누구나 알 수 있을 만큼 명백한 요소에 대해서는 정의를 생략할 수 있습니다. MPML의 정의에 있어서도 white space 라는 요소는 생략되어 있습니다.

: