本家「@IT」にはない内容をエンジニアライフで技術紹介するコラム。広く議論する場になることを目指します。

第016回_構文の整理3_PITarget_Contentspec

»

簡単な検討を要するもの1

今回は基底クラスで実装できない構文やLL (1)でない構文について検討します。

PITarget
PITargetは、EbnfLimitedWordNodeを使って正しく解釈できないので検討が必要になります。

ポイント1
まず呼び出し元となる、PIの構文が
  PI::=SYMBOL ('<?') PITarget (S PI_WORD)? SYMBOL ('?>')
となっています。ここで、SYMBOL ('<?') PITargetの部分に注目します。
SYMBOLトークンは、
 SYMBOL ::= '#FIXED' | '#IMPLIED'| '#PCDATA' | '#REQUIRED'
| '%' | '&#' | '&#x' | '&' | '(' | ')' | ')*'
| '*' | '+' | ',' | '/>' | ';' | '='
| '<' | '</' | '<?' | '<?xml' | '<!['
| '<![CDATA[' | '<!ATTLIST' | '<!DOCTYPE' | '<!ENTITY'
| '<!ELEMENT' | '<!NOTATION'
| '>' | '?' | '?>' | '[' | ']' | ']]>' | '|'

となっており、太字部分の<?xmlと<?は異なるSYMBOLとして抽出する必要があります。方法は字句解析側で考えるとして、そのようなことが可能とすると<?に続くPITargetは xmlでない保証を持ちます。
# もし、<?xmlの並びならSYMBOL ('<?xml')として取得し、
# そうでない場合はSYMBOL ('<?')として取得するので
# SYMBOL ('<?')とWORD ('xml')に分解して取得することはありません。

ポイント2
次にPITargetの構文は
 [17] PITarget ::= Name − (('X' | 'x') ('M' | 'm') ('L' | 'l'))
ですので、
 PITargetはNameかつ、
 もし3文字の場合はxml,Xml,xMl,xmL,XMl,XmL,xML,XMLではいけない
ということを示しています。

総合すると、字句解析器によってPITargetに到達したWORDトークンはxmlを除外できますがXml,xMl,xmL,XMl,XmL,xML,XMLは除外できないため、これを構文NGにする機能がEbnfPITargetNodeに必要です。

Nameの構文は
 [5] Name ::= NameStartChar (NameChar)*
ですから、
 条件1: Name = "^[" + NameStartChar + "][" + NameChar + "]*$"
 条件2: CASE_INSENSITIVE(^xml$)
として、
 PITarget = WORD (条件1 && !条件2)
となります。

# CASE_INSENSITIVE(xml)は、「大文字と小文字を区別しないxmlの文字列に
# マッチする場合」を意味します。よって !CASE_INSENSITIVE(^xml$) は
# 「大文字と小文字を区別せずにxmlの文字列にマッチする場合」を意味します。

contentspec以下の構文
LL (1)で解析できないので、変形が必要です。

まずは自動的な変換を行うと次のようになります。
 contentspec ::= WORD ('EMPTY') | WORD ('ANY') | Mixed | children
 children ::= (choice | seq) ( SYMBOL ('?') | SYMBOL ('*') | SYMBOL ('+') )?
 cp ::= (Name | choice | seq) ( SYMBOL ('?') | SYMBOL ('*') | SYMBOL ('+'))?
 choice ::= SYMBOL ('(') S? cp ( S? SYMBOL ('|') S? cp )+ S? SYMBOL (')')
 seq ::= SYMBOL ('(') S? cp ( S? SYMBOL (',') S? cp )* S? SYMBOL (')')
 Mixed ::= ( SYMBOL ('(') S? SYMBOL ('#PCDATA')
        (S? SYMBOL ('|') S? Name)* S? SYMBOL (')*')
      )
     | ( SYMBOL ('(') S? SYMBOL ('#PCDATA') S? SYMBOL (')') )

contentspecのLL (1)でない部分の確認
contentspecのLL (1)でない部分について確認します。

例えば、
 SYMBOL ('<!ELEMENT') S WORD (name) S contentspec SYMBOL ('>')
の具体値
 <!ELEMENT data EMPTY >
 <!ELEMENT data ANY >
 <!ELEMENT data (#PCDATA) >
 <!ELEMENT data (data0 ) >
のとき、
 SYMBOL ('<!ELEMENT') S WORD ('data') S WORD ('EMPTY')
  S SYMBOL ('>')
 →contentspec EMPTYを適用

 SYMBOL ('<!ELEMENT') S WORD ('data') S WORD ('ANY')
  S SYMBOL ('>')
 →contentspec ANYを適用

 SYMBOL ('<!ELEMENT') S WORD ('data') S SYMBOL ('(')
  SYMBOL ('#PCDATA') SYMBOL (')') S SYMBOL ('>')
 →contentspecからMixedに至り、後半の構文を適用

 SYMBOL ('<!ELEMENT') S WORD ('data') S SYMBOL ('(')
  WORD (data0) SYMBOL (')') S SYMBOL ('>')
 →contentspecからchildrenを通じてseqを適用
と決定する必要があります。

しかし、contentspecの最初のトークンで考えると、
EMPTY: 構文決定
ANY: 構文決定
SYMBOL ('('): Mixedかchildrenのどちらかの'('であって、どちらかは未決定
その他: 構文エラー決定
となり、SYMBOL ('(')を取得した場合に構文が確定しないのでLL (1)ではありません。

contentspecのLL (1)化
Mixedとchildrenの共通部分を括りだすと
  contentspec ::= EMPTY | ANY | ( SYMBOL ('(') S? (Mixed | children) )
となります。
また、この修正により、
 Mixed ::= ( SYMBOL ('#PCDATA')
         (S? SYMBOL ('|') S? Name)* S?
       SYMBOL (')*')
      )
      | ( SYMBOL ('#PCDATA') S? SYMBOL (')') )
と修正します。childrenは先頭の非終端記号のchioceとseqを修整する必要があり、
 choice   ::= cp ( S? SYMBOL ('|') S? cp )+ S? SYMBOL (')')
 seq     ::= cp ( S? SYMBOL (',') S? cp )* S? SYMBOL (')')
となります。さらに、choiceとseqはcpにもあるのでcpも影響を受けて、
 cp     ::= (Name | (SYMBOL ('(') S? choice) | (SYMBOL ('(') S? seq))
         (SYMBOL ('?') | SYMBOL ('*') | SYMBOL ('+'))?
となります。
これでcontentspecはLL (1)の構文になりました。

MixedのLL (1)化
Mixedの新しい構文を見ると、同じようにLL (1)でないことが判ります。
そこで、共通項を括りだすと、
 Mixed ::= SYMBOL ('#PCDATA')
      (
       ( (S? SYMBOL ('|') S? Name)* S? SYMBOL (')*') )
       | (              S? SYMBOL (')') )
      )
となります。さらに後の説明のために非終端記号で構文を分離して。
 Mixed ::= SYMBOL ('#PCDATA') ( MixedA | MixedB )
 MixedA::= (S? SYMBOL ('|') S? Name)* S? SYMBOL (')*')
 MixedB::= S? SYMBOL (')')
とします。

このMixedのLL (1)化は不十分です。なぜならSYMBOL ('#PCDATA')の次に来るトークンは、
S: MixedAまたはMixedBで決定できない
SYMBOL ('|'): MixedA
SYMBOL (')*'): MixedA
SYMBOL (')'): MixedB
となり、S を取得した場合に構文が確定せずLL (1)でないからです。
そこで、MixedAの
  (S? SYMBOL ('|') S? Name)*
の繰り返し部分を0回から3回まで展開してみると、
※具体的にするために3回分展開してみます
0回S? SYMBOL (')*')
1回S? SYMBOL ('|') S? Name S? SYMBOL (')*')
2回S? SYMBOL ('|') S? Name S? SYMBOL ('|') S? Name S? SYMBOL (')*')
3回S? SYMBOL ('|') S? Name S? SYMBOL ('|') S? Name S? SYMBOL ('|') S? Name S? SYMBOL (')*')
・・・
となりますので、 X ::= SYMBOL ('|') S? Name S? とすると、
0回S? SYMBOL (')*')
1回S? X SYMBOL (')*')
2回S? X X SYMBOL (')*')
3回S? X X X SYMBOL (')*')
・・・
になります。
すると最終的に、
 MixedA ::= S? SYMBOL (')*')
 MixedA' ::= S? (SYMBOL ('|') S? Name S?)+ SYMBOL (')*')
に変形できます。
# 後の変形を考えて、MixedAとMixedA'は分けておきます

MixedBと並べると、
 MixedA ::= S? SYMBOL (')*')
 MixedA'::= S? (SYMBOL ('|') S? Name S?)+ SYMBOL (')*')
 MixedB ::= S? SYMBOL (')')
ですから、S?が共通項となり、
 Mixed ::= SYMBOL ('#PCDATA') S? (MixedA | MixedA' | MixedB)
 MixedA ::= SYMBOL (')*')
 MixedA'::= (SYMBOL ('|') S? Name S?)+ SYMBOL (')*')
 MixedB ::= SYMBOL (')')
となります。

単独のトークンになった非終端記号はさらに整理して、
 Mixed ::= SYMBOL ('#PCDATA') S?
       (SYMBOL (')') | SYMBOL (')*') | MixedElemContents)
 MixedElemContents ::= ( SYMBOL ('|') S? Name S? )+ SYMBOL (')*')
とします。

childrenのLL (1)化
 children ::= (choice | seq) (SYMBOL ('?') | SYMBOL ('*') | SYMBOL ('+'))?
 choice ::= cp ( S? SYMBOL ('|') S? cp )+ S? SYMBOL (')')
 seq ::= cp ( S? SYMBOL (',') S? cp )* S? SYMBOL (')')
 cp ::= (Name | (SYMBOL ('(') S? choice) | (SYMBOL ('(') S? seq))
        (SYMBOL ('?') | SYMBOL ('*') | SYMBOL ('+'))?
において、childrenの先頭はchoiceかseqのAlternative句をとり、
かつ、choiceとseqの最初のトークンはどちらもcpであり、続くトークンがRepetition句からなります。
つまり、childrenはLL (1)ではありませんので、LL (1)に変形してみます。
まず、choiceとseqのRepetition句を展開してcp周りを整理します。
choice ::=
0回cp S? SYMBOL ('|') S? cp S? SYMBOL (')')
1回cp S? SYMBOL ('|') S? cp S? SYMBOL ('|') S? cp S? SYMBOL (')')
2回cp S? SYMBOL ('|') S? cp S? SYMBOL ('|') S? cp S? SYMBOL ('|') S? cp S? SYMBOL (')')
・・・
seq ::=
0回cp S? SYMBOL (')')
1回cp S? SYMBOL (',') S? cp S? SYMBOL (')')
2回cp S? SYMBOL (',') S? cp S? SYMBOL (',') S? cp S? SYMBOL (')')
・・・
となります。先頭のcp S? を除き繰り返し部分を抽出して
 choiceTerm ::= SYMBOL ('|') S? cp S?
 seqTerm  ::= SYMBOL (',') S? cp S?
を非終端記号とすると、
choice ::=
0回cp S? choiceTerm SYMBOL (')')
1回cp S? choiceTerm choiceTerm SYMBOL (')')
2回cp S? choiceTerm choiceTerm choiceTerm SYMBOL (')')
・・・
seq ::=
0回cp S? SYMBOL (')')
1回cp S? seqTerm SYMBOL (')')
2回cp S? seqTerm seqTerm SYMBOL (')')
・・・
となりますので、
 choice ::= cp S? choiceTerm+ SYMBOL (')')
 seq  ::= cp S? seqTerm*  SYMBOL (')')
に整理できます。よってchildrenの先頭のAlternative句は
 (choice | seq) ::= cp S? (choiceTerm+ | seqTerm*) SYMBOL (')')
となります。

さてchoiceTermの繰り返しが+でseqTermの繰り返しが*なのは、childrenで単項(cpだけからなる)の場合に、seqTerm側の構文を通るようにするためです。上記の(choice | seq)句では、単項のcpを括りだしているのでその必要はありません。よって
 (choice | seq) ::= cp S? (choiceTerm+ | seqTerm+)? SYMBOL (')')
のように変形して繰り返し記号をあわせます。

最終的に、
children::=cp S? (choiceTerm+ | seqTerm+)? SYMBOL (')')
(SYMBOL ('?') | SYMBOL ('*') | SYMBOL ('+'))?
choiceTerm::=SYMBOL ('|') S? cp S?
seqTerm::=SYMBOL (',') S? cp S?
となります。

cpの整理
cpを整理します。
 cp ::= (Name | (SYMBOL ('(') S? choice) | (SYMBOL ('(') S? seq))
      (SYMBOL ('?') | SYMBOL ('*') | SYMBOL ('+'))?
から、
 cp ::= (Name | ( SYMBOL ('(') S? (choice | seq) ) )
      (SYMBOL ('?') | SYMBOL ('*') | SYMBOL ('+'))?
に変形して、さらにchildrenのLL (1)化で
 (choice | seq) ::= cp S? (choiceTerm+ | seqTerm+)? SYMBOL (')')
としたので、
 cp ::= (Name
    | ( SYMBOL ('(') S? cp S? (choiceTerm+ | seqTerm+)? SYMBOL (')') )
     )
     (SYMBOL ('?') | SYMBOL ('*') | SYMBOL ('+'))?
となります。

cpにchildrenを導入する
cpは SYMBOL ('(')とSYMBOL (')')で囲んだ部分にchildrenを書くことができます。
そこで、共通項を
 modifier ::= (SYMBOL ('?') | SYMBOL ('*') | SYMBOL ('+'))?
とすると
children::=cp S? (choiceTerm+ | seqTerm*)? SYMBOL (')') modifier
cp::=(Name
 | ( SYMBOL ('(') S? cp S? (choiceTerm+ | seqTerm+)?
  SYMBOL (')') )
)
modifier
になります。cpは次のように形を変えると、
 cp ::= (Name modifier)
     | (SYMBOL ('(') S? cp S? (choiceTerm+ | seqTerm+)? SYMBOL (')')
     modifier )
とでき、childrenを含みますから
 cp ::= (Name modifier) | ( SYMBOL ('(') S? children )
となります。非終端記号cpName,multiTermを導入すると、
 cp    ::= (cpName | multiTerm)
 cpName  ::= Name modifier
 multiTerm ::= SYMBOL ('(') S? children
になります。

multiTermの開き括弧と閉じ括弧を同じ構文に入れる
multiTermは、開き括弧はあるのに、同じ構文に閉じ括弧がありません。
そこでchildrenとmultiTermを修正して括弧を同じ構文内に収めます。
 term ::= cp S? (choiceTerm+ | seqTerm+)?
とすると、
 multiTerm ::= SYMBOL ('(') S? term S? SYMBOL (')') modifier
 children ::= term S? SYMBOL (')') modifier
に整理できます。

modifierとトークン粒度の整理およびここまでの構文のまとめ
modifierは説明するために必要だったので導入しましたが
実際は SYMBOL (')') modifierが連続する場合は、1つのトークンになります。※cpName用のmodifierは残ります。
よって、ここまでの検討を経て
クラス名整理した構文
EbnfMixedElemContentsNodeMixedElemContents ::=
( SYMBOL ('|') S? Name S? )+ SYMBOL (')*')
EbnfMixedNodeMixed ::= SYMBOL ('#PCDATA') S?
( SYMBOL (')')
| SYMBOL (')*')
| MixedElemContents )
EbnfModifierNodemodifier ::=
( SYMBOL ('?') | SYMBOL ('*') | SYMBOL ('+') )?
EbnfCpNodecp ::= (cpName | multiTerm)
EbnfMultiTermNodemultiTerm ::= SYMBOL ('(') S? term S?
( SYMBOL (')')
| SYMBOL (')?')
| SYMBOL (')*')
| SYMBOL (')+') )
EbnfTermNodeterm ::= cp S? (choiceTerm+ | seqTerm+)?
EbnfCpNameNodecpName ::= Name modifier
EbnfChildrenNodechildren ::= term S?
( SYMBOL (')')
| SYMBOL (')?')
| SYMBOL (')*')
| SYMBOL (')+') )
EbnfChoiceTermNodechoiceTerm ::= SYMBOL ('|') S? cp S?
EbnfSeqTermNodeseqTerm ::= SYMBOL (',') S? cp S?
EbnfContentspecNodecontentspec ::= WORD ('EMPTY')| WORD ('ANY') | ( SYMBOL ('(') S? ( Mixed | children ) )
となります。

Symbolの追加

')'とmodifierが一つのトークンになったので、')?'と')+'を追加します。
SYMBOL ::= '#FIXED'
| '#IMPLIED'
| '#PCDATA'
| '#REQUIRED'
| '%'
| '&#'
| '&#x'
| '&'
| '('
| ')'
| ')?' //追加
| ')*'
| ')+' //追加
| '*'
| '+'
| ','
| '/>'
| ';'
| '='
| '<'
| '</'
| '<?'
| '<?xml'
| '<!['
| '<![CDATA['
| '<!ATTLIST'
| '<!DOCTYPE'
| '<!ENTITY'
| '<!ELEMENT'
| '<!NOTATION'
| '>'
| '?'
| '?>'
| '['
| ']'
| ']]>'
| '|'

Comment(0)

コメント

コメントを投稿する