以前の版
2020-02-09 23:31 時点における版
<Header>
<Parent> HowItWorks
<Title> パーサー
<CreatedAt> 2019-11-23
<Tags> 構文解析, アルゴリズム
<Summary>
OutlineTextのパーサ(Parser)について
</Summary>
</Header>
# はじめに
OutlineTextは,2018年4月から開発され,明確なパーサモデルがないまま,
トライアル・アンド・エラーで構築,改良されていきました.
本稿で挙げられているパーサモデルはその過程で作られたものです.
特にモデルは理想や目標で語られることが多く^[注.model],実際のスクリプトがモデルに追い付いていない
ことがあります(特にクラスの分離や,命名など).ご容赦ください.
# 文法内の要素について
OutlineTextの記法には,多くのほかの記法と同じように,大きく分けて二つの要素があります.
一つめは,インライン要素で,二つ目は,ブロック要素です.
インライン要素:
行内にある記法.行をまたがない記法
ex)
```xml
**強調**, //アクセント//, __アンダーライン__,
[URL](url), など
```
ブロック要素:
複数行にわたる記法.行をまたぐ記法.
ex)
```xml
* リスト
* リスト
段落
段落
段落
段落
上の段落を合わせて
一つのセクションというブロック要素
定義リスト:
定義用語
```

# イベント駆動型モジュラーパーサ(Event-driven Modular Parser)
OutlineTextのパーサは,一つのパーサがすべての文法を解釈するのではなく,
複数のパーサが組み合わさって動作します.パーサの種類は大きく分けて三種類あり,
//メインパーサ//,//ブロック要素パーサ//,//インライン要素パーサ//があります.
ブロック要素パーサとインライン要素パーサは,
メインパーサが発火させる//イベントを受けて動作//します.

# パーサの種類
メインパーサ:
文書を読み込み,イベント(インデントが入った, 抜けた, 行の先頭にきたなど)
を発火させる.
ブロック要素パーサ:
ブロック要素を担当するパーサ.メインパーサが発火させるイベントに反応して処理を実行する.
この種類のパーサは,単体ではなく,ブロック要素ごとに一つのパーサが担当する.
インライン要素パーサ:
インライン要素を担当するパーサ.
メインパーサとブロック要素から呼ばれる.
インライン文法の変換テーブルを参照して,プレインテキストを変換する.
複数のインライン文法があっても,このパーサは一つ.
# イベントの種類
メインパーサは,読み込まれた文章を上から読み,行の変化やインデントの変化
等があったときにイベントを発火します.

# 優先順位
イベント時,メインパーサが呼ぶブロック要素パーサには順番があります.
そのイベントに対して優先度の高いパーサから順番に呼ばれていきます.
呼ばれたパーサは,処理を行いそのあとのパーサに処理を回すか選択できます.
処理を回すことを選択すると,メインパーサは,続けて次に優先度の高いパーサを呼び出します.
逆に処理を回さないことを選択すると,メインパーサは,それ以降のパーサを呼び出しません.
例えば,行頭のイベントでは,まず見出しに関するパーサが呼ばれたあと,段落に関するパーサが呼ばれます.
実際に,行頭が見出しの場合,見出しパーサがその行をデコードし,それより後のパーサには処理を回しません.

# 文脈(Context)
各パーサが独立しているため,あるパーサが持っている文書の情報をほかのパーサが参照できる
仕組みが必要です.そこで,文脈(Context)というデータが登場します.
パーサは理解した文書の情報を文脈に書き込みます.
ほかのパーサは,文脈を見て,しかるべき動作をします.

# 処理の流れ
1. メインパーサが文書読み込み
2. デコード単位(チャンク)に分ける
3. チャンクごとにデコード開始.
3.1. メインパーサがイベント発火
3.2. イベントを受けとったブロック要素パーサ,インライン要素パーサがデコードを行う.
4. 終了
# 文法の追加
各パーサが独立して動作する//イベント駆動型モジュラーパーサ//を採用しているため,
文法の追加は,難しくありません.
# ブロック文法
1. ブロック要素パーサの基底クラスを継承してパーサの実装
```php
/**
* 空行を線で区切る(誰得?)
*/
class SeparateEmptyParser extends ElementParser {
public static function OnEmptyLine($context, &$output) {
$output = '<hr>';
return false;
}
}
```
2. メインパーサにイベントの登録
```php
private static $onEmptyLineParserList = [
'HeadingElementParser',
'ParagraphElementParser',
'TableElementParser',
'ReferenceListParser',
'SeparateEmptyParser', // <- 追加
];
```
# インライン文法
1. インライン文法変換テーブルに追加
```php
private static $spanElementPatternTable = [
["/\[\[ *(.*?) *\]\]/", '<a name="{0}"></a>', null],
["/\[(.*?)\]\((.*?)\)/", null, 'DecodeLinkElementCallback'],
["/\*\*(.*?)\*\*/", '<strong>{0}</strong>', null],
["/\/\/(.*?)\/\//", '<em>{0}</em>', null],
["/__(.*?)__/", '<mark>{0}</mark>', null],
["/~~(.*?)~~/", '<del>{0}</del>', null],
["/\^\[(.*?)\]/", null, 'DecodeReferenceElementCallback'],
["/<((http|https):\/\/[0-9a-z\-\._~%\:\/\?\#\[\]@\!\$&'\(\)\*\+,;\=]+)>/i", '<a href="{0}">{0}</a>', null],
["/<(([a-zA-Z0-9])+([a-zA-Z0-9\?\*\[|\]%'=~^\{\}\/\+!#&\$\._-])*@([a-zA-Z0-9_-])+\.([a-zA-Z0-9\._-]+)+)>/", '<a href="mailto:{0}">{0}</a>', null],
["/->/", '→', null],
["/<-/", '←', null],
["/=>/", '⇒', null],
["/<=/", '⇐', null],
["/\.\.\./", '…', null],
["/--/", '—', null],
["/\(TM\)/", '™', null],
["/\(R\)/", '®', null],
["/\(C\)/", '©', null],
["/'/", '’', null],
["/です/", 'ぽよ', null], // <- 追加. 'です'を'ぽよ'に変換する
];
```
# 注釈
[注.model]: 筆者独自解釈