---
parent: HowItWorks
title: パーサー
date: 2019-11-23
tags: 構文解析, アルゴリズム
---

OutlineTextのパーサ(Parser)について

===

# はじめに
    OutlineTextは,2018年4月から開発され,明確なパーサモデルがないまま,
    トライアル・アンド・エラーで構築,改良されていきました.
    本稿で挙げられているパーサモデルはその過程で作られたものです.
    特にモデルは理想や目標で語られることが多く^[注.model],実際のスクリプトがモデルに追い付いていない
    ことがあります(特にクラスの分離や,命名など).ご容赦ください.

# 文法内の要素について
    OutlineTextの記法には,多くのほかの記法と同じように,大きく分けて二つの要素があります.
    一つめは,インライン要素で,二つ目は,ブロック要素です.
    
    インライン要素:
        行内にある記法.行をまたがない記法
        
        ex)
            ```xml
                **強調**, //アクセント//, __アンダーライン__, 
                [URL](url), など
            ```
        
    ブロック要素:
        複数行にわたる記法.行をまたぐ記法.
        
        ex)
            ```xml
                * リスト
                * リスト
                
                段落
                段落
                    
                    段落
                    段落
                    
                    上の段落を合わせて
                    一つのセクションというブロック要素
                    
                定義リスト:
                    定義用語
                    
            ```
    
    ![要素の種類](CURRENT_DIR/Images/elements.jpg)

# イベント駆動型モジュラーパーサ(Event-driven Modular Parser)
    OutlineTextのパーサは,一つのパーサがすべての文法を解釈するのではなく,
    複数のパーサが組み合わさって動作します.パーサの種類は大きく分けて三種類あり,
    //メインパーサ//,//ブロック要素パーサ//,//インライン要素パーサ//があります.
    ブロック要素パーサとインライン要素パーサは,
    メインパーサが発火させる//イベントを受けて動作//します.
    
    ![Event-driven Modular Parser](CURRENT_DIR/Images/modular-parser.jpg)
    
    # パーサの種類
        メインパーサ:
            文書を読み込み,イベント(インデントが入った, 抜けた, 行の先頭にきたなど)
            を発火させる.
        
        ブロック要素パーサ:
            ブロック要素を担当するパーサ.メインパーサが発火させるイベントに反応して処理を実行する.
            この種類のパーサは,単体ではなく,ブロック要素ごとに一つのパーサが担当する.
        
        インライン要素パーサ:
            インライン要素を担当するパーサ.
            メインパーサとブロック要素から呼ばれる.
            インライン文法の変換テーブルを参照して,プレインテキストを変換する.
            複数のインライン文法があっても,このパーサは一つ.
    
    # イベントの種類
        メインパーサは,読み込まれた文章を上から読み,行の変化やインデントの変化
        等があったときにイベントを発火します.
        
        ![Event の種類](CURRENT_DIR/Images/events.jpg)
    
    # 優先順位
        イベント時,メインパーサが呼ぶブロック要素パーサには順番があります.
        そのイベントに対して優先度の高いパーサから順番に呼ばれていきます.
        呼ばれたパーサは,処理を行いそのあとのパーサに処理を回すか選択できます.
        処理を回すことを選択すると,メインパーサは,続けて次に優先度の高いパーサを呼び出します.
        逆に処理を回さないことを選択すると,メインパーサは,それ以降のパーサを呼び出しません.
        
        例えば,行頭のイベントでは,まず見出しに関するパーサが呼ばれたあと,段落に関するパーサが呼ばれます.
        実際に,行頭が見出しの場合,見出しパーサがその行をデコードし,それより後のパーサには処理を回しません.
        
        ![優先順位](CURRENT_DIR/Images/priority.jpg)
        
        
    # 文脈(Context)
        各パーサが独立しているため,あるパーサが持っている文書の情報をほかのパーサが参照できる
        仕組みが必要です.そこで,文脈(Context)というデータが登場します.
        パーサは理解した文書の情報を文脈に書き込みます.
        ほかのパーサは,文脈を見て,しかるべき動作をします.
        
        ![文脈(Context)](CURRENT_DIR/Images/context.jpg)
    
        
# 処理の流れ
    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],
                    ["/->/", '&#8594;', null],
                    ["/<-/", '&#8592;', null],
                    ["/=>/", '&#8658;', null],
                    ["/<=/", '&#8656;', null],
                    ["/\.\.\./", '&#8230;', null],
                    ["/--/", '&#8212;', null],
                    ["/\(TM\)/", '&#8482;', null],
                    ["/\(R\)/", '&#174;', null],
                    ["/\(C\)/", '&#169;', null],
                    ["/'/", '&#8217;', null],
                    ["/です/", 'ぽよ', null], // <- 追加. 'です'を'ぽよ'に変換する
                ];
            ```
# 注釈
    [注.model]: 筆者独自解釈