質問編集履歴

2 著しい表示崩れを確認したため、修正

sounisi5011

sounisi5011 score 716

2015/11/29 18:33  投稿

Lemon ParserでCSSパーサーっぽいものが作れない
PHP_ParserGeneratorを使用し、CSSに似た構文構造のファイルを解析するパーサーを作ろうとしています。
(なお、PHP_ParserGeneratorはLemon ParserをPHPに実装したもののため、[Lemon Parserのドキュメントの日本語訳](http://www.geocities.jp/beruponu/memo/lemonparser/document_japanese.html)を読みながら作成しています。)
サンプルとして、以下の様なファイルを解析することを想定しています。
```lang-text
```
Selector {
   start-tag '<div>'
   raw-content "example"
   end-tag '</div>'
}
/*
こめんと
*/
Selector {
   element '<div>
   \"exa\\mple\'
</div>'
   /* ここで使えるか? */
   a 'a'
   b 'a'
   c 'a'
   d 'a'
   e 'a'
   f 'a'
   g 'a'
}
```
CSSと同じような構造のファイルです。
`Selector`は[Selectors Level 3](http://www.w3.org/TR/selectors/)(CSS3セレクタ)に対応する予定ですが、今のところは`Selector`の固定文字列で動作させています。
`Selector`の後には、CSSと同じく`{`と`}`で囲まれた宣言ブロック内に、プロパティと値が記述されています。
CSSとの相違点として、プロパティと値の区切りに`:`ではなくスペースを使用している点と、宣言(プロパティと値を組み合わせたもの)の区切りに`;`ではなく改行を使用してる点が挙げられます。
これを解析するため、実際に書いた`.y`ファイルは以下になります。
```lang-text
```
%name MyParser
%declare_class { class MyParser }
/**
* エラー時の処理
* スタックの内容をecho文で出力した後、例外を発生させる
*/
%syntax_error {
   echo 'Syntax Error';
   if (isset($this->lex)) {
       echo ' on line ', $this->lex->line, ": token '", $this->lex->value, "'";
   }
   echo ' while parsing rule:', "\n\n";
   $rules = array();
   foreach ($this->yystack as $entry) {
       $major = $this->tokenName($entry->major);
       ob_start();
       var_dump($entry->minor);
       $minor = preg_replace(
           '/^/m',
           ' ',
           trim(ob_get_clean(), "\r\n")
       );
       $rules[] = $major.":\n".$minor;
   }
   echo implode("\n\n", $rules);
   foreach ($this->yy_get_expected_tokens($yymajor) as $token) {
       $expect[] = self::$yyTokenName[$token];
   }
   throw new Exception(
       'Unexpected ' . $this->tokenName($yymajor) . '(' . $TOKEN . '), expected one of: ' . implode(',', $expect)
   );
}
%include_class {
   private $ast;
   function getAst()
   {
       return $this->ast;
   }
}
/* ***** ***** ***** ***** ***** ***** ***** ***** ***** ***** */
/**
* Note: 字句解析器で定義しているトークンの正規表現です
*/
// SELECTOR = /Selector/
// IDENT = /-?[_a-zA-Z][_a-zA-Z0-9-]*/
// COMMENT = @//[^\r\n\f]*|/\*[^*]*\*+(?:[^/*][^*]*\*+)*/@
// APOS_STRING = /'(?:[^'\\]|\\[^']|\\['\\])*'/
// QUOT_STRING = /"(?:[^"\\]|\\[^"]|\\["\\])*"/
// INLINE_SPACE = /[ \t]+/
// NL = /\r\n|\r|\n|\f/
// L_CURLY_BRACKET = "{"
// R_CURLY_BRACKET = "}"
/* ***** ***** ***** ***** ***** ***** ***** ***** ***** ***** */
template ::= statementList(A). {
   $this->ast = A;
}
/**
* 宣言
*
* statementList: statement+
* statement: ruleset | COMMENT | s
*/
statementList(A) ::= statement(B). { A = B; }
statementList(A) ::= statementList(B) statement(C). {
   A = array_merge((array)A, (array)B, (array)C);
}
statement(A) ::= ruleset(B). { A = array(B); }
statement ::= COMMENT.
statement ::= s.
/**
* ruleset: selector s? declarationBlock
*/
ruleset(A) ::= selector(B) s_zo declarationBlock(C). {
   A = array(
       'selector' => B,
       'declarations' => C,
   );
}
/**
* セレクタ
* Selectors Level 3対応
*
* selector: SELECTOR
*/
selector(A) ::= SELECTOR(B). { A = B; }
/**
* 宣言
*
* declarationBlock: "{" s? declarationList s? "}"
* declarationList: declaration [ s? NL s? declaration ]*
* declaration: property INLINE_SPACE value | COMMENT
*/
declarationBlock(A) ::= L_CURLY_BRACKET s_zo declarationList(B) s_zo R_CURLY_BRACKET. { A = B; }
// 宣言同士は改行で区切ります
declarationList(A) ::= declaration(B). { A = B; }
declarationList(A) ::= declarationList(B) inline_space_zo NL s_zo declaration(C). {
   A = array_merge((array)A, (array)B, (array)C);
}
// プロパティと値はスペースで区切ります
declaration(A) ::= property(B) INLINE_SPACE value(C). {
   A = array(
       B => C,
   );
}
// コメントも宣言の一種とみなします
declaration ::= COMMENT.
/**
* プロパティ
*
* property: IDENT
*/
property(A) ::= IDENT(B). { A = B; }
/**
* 値
*
* value: APOS_STRING | QUOT_STRING
*/
/* アポストロフィー(シングルクォート)で囲まれた文字列 */
value(A) ::= APOS_STRING(B). {
   A = array(
       'string',
       strtr(substr(B, 1, -1), array("\\'" => "'", '\\\\' => '\\')),
   );
}
/* クォーテーションマーク(ダブルクォート)で囲まれた文字列 */
value(A) ::= QUOT_STRING(B). {
   A = array(
       'string',
       strtr(substr(B, 1, -1), array('\\"' => '"', '\\\\' => '\\')),
   );
}
/**
* 空白文字と改行
*
* s: space_chars+
* space_chars: INLINE_SPACE | NL
*/
s ::= space_chars.
s ::= s space_chars.
space_chars ::= INLINE_SPACE.
space_chars ::= NL.
/**
* s?
*/
s_zo ::= .
s_zo ::= s.
/**
* INLINE_SPACE?
*/
inline_space_zo ::= INLINE_SPACE.
inline_space_zo ::= .
```
以上を作成し実際に実行してみたのですが、エラーとなります。
ブラウザ上には、`%syntax_error`内で定義している通りに動作した結果以下の文字列が出力され
```lang-text
```
Syntax Error while parsing rule:
End of Input:
 NULL
selector:
 string(8) "Selector"
s_zo:
 NULL
L_CURLY_BRACKET:
 string(1) "{"
s_zo:
 NULL
declarationList:
 array(1) {
   ["start-tag"]=>
   array(2) {
     [0]=>
     string(6) "string"
     [1]=>
     string(5) "<div>"
   }
 }
s_zo:
 NULL
```
例外のメッセージには以下の様な文面が出力されています。
Unexpected IDENT(raw-content), expected one of: R_CURLY_BRACKET
ここから予想できるのは、`{`と`}`で囲まれた範囲内の`declarationList`を解釈する場合に、`start-tag '<div>'`を解釈した時点で`declarationList`が終了したものと謝って解釈しているためと考えられます。
本来、`declarationList`は`declaration`を改行文字(と、その間にある空白文字)で区切った構造体であり、まだ`raw-content "example"`と`end-tag '</div>'`が残っています。
にも関わらず、すでに終了したと誤って解釈されており、`start-tag '<div>'`と`raw-content "example"`の間の改行(+空白文字)が`s_zo`と解釈され、結果、直後には`declarationBlock`を閉じるはずの`R_CURLY_BRACKET`(すなわち`}`)が存在せず、パースエラーが発生しています。
この問題を解決するには、`declarationList`内の改行を優先的に識別させなければなりません。
このための記述として、Lemon Parserには`%left`,`%right`,`%nonassoc`が定義されており、`NL`を指定すればいいように思えます。
しかし、0文字以上の空白文字を示す`s_zo`内でも`NL`を使用しているため、結局解決しません。
事実、`%left NL.`の記述を追加しても動作しませんでした。
余分な空白文字を無視しないことが問題をややこしくしていますが、スペースも改行も宣言やプロパティと値の区切りに使用しており、無視できるものではありません。
これはどう解決すれば良いのでしょうか?
  • PHP

    36806 questions

    PHPは、Webサイト構築に特化して開発されたプログラミング言語です。大きな特徴のひとつは、HTMLに直接プログラムを埋め込むことができるという点です。PHPを用いることで、HTMLを動的コンテンツとして出力できます。HTMLがそのままブラウザに表示されるのに対し、PHPプログラムはサーバ側で実行された結果がブラウザに表示されるため、PHPスクリプトは「サーバサイドスクリプト」と呼ばれています。

1 誤字を修正

sounisi5011

sounisi5011 score 716

2015/06/14 07:16  投稿

Lemon ParserでCSSパーサーっぽいものが作れない
PHP_ParserGeneratorを使用し、CSSに似た構文構造のファイルを解析するパーサーを作ろうとしています。
(なお、PHP_ParserGeneratorはLemon ParserをPHPに実装したもののため、[Lemon Parserのドキュメントの日本語訳](http://www.geocities.jp/beruponu/memo/lemonparser/document_japanese.html)を読みながら作成しています。)
サンプルとして、以下の様なファイルを解析することを想定しています。
```lang-text
Selector {
   start-tag '<div>'
   raw-content "example"
   end-tag '</div>'
}
/*
こめんと
*/
Selector {
   element '<div>
   "exa\mple'
</div>'
   /* ここで使えるか$1 */
   a 'a'
   b 'a'
   c 'a'
   d 'a'
   e 'a'
   f 'a'
   g 'a'
}
```
CSSと同じような構造のファイルです。
 
`Selector`は[Selectors Level 3](http://www.w3.org/TR/selectors/)(CSS3セレクタ)に対応する予定ですが、今のところは`Selector`の固定文字列で動作させています。
 
`Selector`の後には、CSSと同じく`{`と`}`で囲まれた宣言ブロック内に、プロパティと値が記述されています。
CSSとの相違点として、プロパティと値の区切りに`:`ではなくスペースを使用している点と、宣言(プロパティと値を組み合わせたもの)の区切りに`;`ではなく改行を使用しています。
CSSとの相違点として、プロパティと値の区切りに`:`ではなくスペースを使用している点と、宣言(プロパティと値を組み合わせたもの)の区切りに`;`ではなく改行を使用してる点が挙げられます。
これを解析するため、実際に書いた`.y`ファイルは以下になります。
```lang-text
%name MyParser
%declare_class { class MyParser }
/**
* エラー時の処理
* スタックの内容をecho文で出力した後、例外を発生させる
*/
%syntax_error {
   echo 'Syntax Error';
   if (isset($this->lex)) {
       echo ' on line ', $this->lex->line, ": token '", $this->lex->value, "'";
   }
   echo ' while parsing rule:', "nn";
   $rules = array();
   foreach ($this->yystack as $entry) {
       $major = $this->tokenName($entry->major);
       ob_start();
       var_dump($entry->minor);
       $minor = preg_replace(
           '/^/m',
           ' ',
           trim(ob_get_clean(), "rn")
       );
       $rules[] = $major.":n".$minor;
   }
   echo implode("nn", $rules);
   foreach ($this->yy_get_expected_tokens($yymajor) as $token) {
       $expect[] = self::$yyTokenName[$token];
   }
   throw new Exception(
       'Unexpected ' . $this->tokenName($yymajor) . '(' . $TOKEN . '), expected one of: ' . implode(',', $expect)
   );
}
%include_class {
   private $ast;
   function getAst()
   {
       return $this->ast;
   }
}
/* ***** ***** ***** ***** ***** ***** ***** ***** ***** ***** */
/**
* Note: 字句解析器で定義しているトークンの正規表現です
*/
// SELECTOR = /Selector/
// IDENT = /-$2[_a-zA-Z][_a-zA-Z0-9-]*/
// COMMENT = @//[^rnf]*|/*[^*]**+($3:[^/*][^*]**+)*/@
// APOS_STRING = /x27($4:[^x27\]|\[^x27]|\[x27\])*x27/
// APOS_STRING = /'($5:[^'\]|\[^']|\['\])*'/
// QUOT_STRING = /"(?:[^"\]|\[^"]|\["\])*"/
// INLINE_SPACE = /[ t]+/
// NL = /rn|r|n|f/
// L_CURLY_BRACKET = "{"
// R_CURLY_BRACKET = "}"
/* ***** ***** ***** ***** ***** ***** ***** ***** ***** ***** */
template ::= statementList(A). {
   $this->ast = A;
}
/**
* 宣言
*
* statementList: statement+
* statement: ruleset | COMMENT | s
*/
statementList(A) ::= statement(B). { A = B; }
statementList(A) ::= statementList(B) statement(C). {
   A = array_merge((array)A, (array)B, (array)C);
}
statement(A) ::= ruleset(B). { A = array(B); }
statement ::= COMMENT.
statement ::= s.
/**
* ruleset: selector s? declarationBlock
*/
ruleset(A) ::= selector(B) s_zo declarationBlock(C). {
   A = array(
       'selector' => B,
       'declarations' => C,
   );
}
/**
* セレクタ
* Selectors Level 3対応
*
* selector: SELECTOR
*/
selector(A) ::= SELECTOR(B). { A = B; }
/**
* 宣言
*
* declarationBlock: "{" s? declarationList s? "}"
* declarationList: declaration [ s? NL s? declaration ]*
* declaration: property INLINE_SPACE value | COMMENT
*/
declarationBlock(A) ::= L_CURLY_BRACKET s_zo declarationList(B) s_zo R_CURLY_BRACKET. { A = B; }
// 宣言同士は改行で区切ります
declarationList(A) ::= declaration(B). { A = B; }
declarationList(A) ::= declarationList(B) inline_space_zo NL s_zo declaration(C). {
   A = array_merge((array)A, (array)B, (array)C);
}
// プロパティと値はスペースで区切ります
declaration(A) ::= property(B) INLINE_SPACE value(C). {
   A = array(
       B => C,
   );
}
// コメントも宣言の一種とみなします
declaration ::= COMMENT.
/**
* プロパティ
*
* property: IDENT
*/
property(A) ::= IDENT(B). { A = B; }
/**
* 値
*
* value: APOS_STRING | QUOT_STRING
*/
/* アポストロフィー(シングルクォート)で囲まれた文字列 */
value(A) ::= APOS_STRING(B). {
   A = array(
       'string',
       strtr(substr(B, 1, -1), array("\'" => "'", '\\' => '\')),
   );
}
/* クォーテーションマーク(ダブルクォート)で囲まれた文字列 */
value(A) ::= QUOT_STRING(B). {
   A = array(
       'string',
       strtr(substr(B, 1, -1), array('\"' => '"', '\\' => '\')),
   );
}
/**
* 空白文字と改行
*
* s: space_chars+
* space_chars: INLINE_SPACE | NL
*/
s ::= space_chars.
s ::= s space_chars.
space_chars ::= INLINE_SPACE.
space_chars ::= NL.
/**
* s?
*/
s_zo ::= .
s_zo ::= s.
/**
* INLINE_SPACE?
*/
inline_space_zo ::= INLINE_SPACE.
inline_space_zo ::= .
```
以上を作成し実際に実行してみたのですが、エラーとなります。
ブラウザ上には、`%syntax_error`内で定義している通りに動作した結果以下の文字列が出力され
```lang-text
Syntax Error while parsing rule:
End of Input:
 NULL
selector:
 string(8) "Selector"
s_zo:
 NULL
L_CURLY_BRACKET:
 string(1) "{"
s_zo:
 NULL
declarationList:
 array(1) {
   ["start-tag"]=>
   array(2) {
     [0]=>
     string(6) "string"
     [1]=>
     string(5) "<div>"
   }
 }
s_zo:
 NULL
```
例外のメッセージには以下の様な文面が出力されています。
Unexpected IDENT(raw-content), expected one of: R_CURLY_BRACKET
ここから予想できるのは、`{`と`}`で囲まれた範囲内の`declarationList`を解釈する場合に、
`start-tag '<div>'`を解釈した時点で`declarationList`が終了したものと謝って解釈しているためと考えられます。
本来、`declarationList`は`declaration`を改行文字(と、その間にある空白文字)で区切った構造体であり、
まだ`raw-content "example"`と`end-tag '</div>'`が残っています。
にも関わらず、すでに終了したと誤って解釈されており、`start-tag '<div>'`と`raw-content "example"`の間の改行(+空白文字)が`s_zo`と解釈され、
結果、直後には`declarationBlock`を閉じるはずの`R_CURLY_BRACKET`(すなわち`}`)が存在せず、パースエラーが発生しています。
ここから予想できるのは、`{`と`}`で囲まれた範囲内の`declarationList`を解釈する場合に、`start-tag '<div>'`を解釈した時点で`declarationList`が終了したものと謝って解釈しているためと考えられます。
本来、`declarationList`は`declaration`を改行文字(と、その間にある空白文字)で区切った構造体であり、まだ`raw-content "example"`と`end-tag '</div>'`が残っています。
にも関わらず、すでに終了したと誤って解釈されており、`start-tag '<div>'`と`raw-content "example"`の間の改行(+空白文字)が`s_zo`と解釈され、結果、直後には`declarationBlock`を閉じるはずの`R_CURLY_BRACKET`(すなわち`}`)が存在せず、パースエラーが発生しています。
この問題を解決するには、`declarationList`内の改行を優先的に識別させなければなりません。
このための記述として、Lemon Parserには`%left`,`%right`,`%nonassoc`が定義されており、`NL`を指定すればいいように思えます。
しかし、0文字以上の空白文字を示す`s_zo`内でも`NL`を使用しているため、結局解決しません。
事実、`%left NL.`の記述を追加しても動作しませんでした。
余分な空白文字を無視しないことが問題をややこしくしていますが、スペースも改行も宣言やプロパティと値の区切りに使用しており、無視できるものではありません。
これはどう解決すれば良いのでしょうか?
  • PHP

    36806 questions

    PHPは、Webサイト構築に特化して開発されたプログラミング言語です。大きな特徴のひとつは、HTMLに直接プログラムを埋め込むことができるという点です。PHPを用いることで、HTMLを動的コンテンツとして出力できます。HTMLがそのままブラウザに表示されるのに対し、PHPプログラムはサーバ側で実行された結果がブラウザに表示されるため、PHPスクリプトは「サーバサイドスクリプト」と呼ばれています。

思考するエンジニアのためのQ&Aサイト「teratail」について詳しく知る