正規表現の変形で作る独自記法WiKi Parser

@shokai
右のメニューのStart Presentationで
スライドになります
応募した内容
感想など
今日の話
1. 独自記法をどんどん作って乱立させよう
2. その実装方法
元々20分ぐらいライブコーディングで説明してもうまくまとまらなかった話を飛び入りで5分にまとめてリベンジしにきた
なぜ独自記法?
ページ間リンクが最も重要な機能だから
1ページ内の装飾よりも
[
も (
も全部予約されてる
じゃあ全部作ろう
Scrapbox記法
[builderscon]
見た目はhashtagだけど内部的にはリンク記法と同じ
書いたそばからリンク構造を辿って、推薦が更新されていく
記法は重要
ドキュメントツールにはコンセプトに最適化した記法が必要
体験が中途半端になるので、リンク記法を最優先したい
必ずしもMarkdownが最適では無いし、そもそもプログラマしか使ってない
例: <img>
に <a>
を付けたい
Markdown
[](リンク先URL)
Scrapbox記法
[画像URL リンク先URL]
もしくは
[リンク先URL 画像URL]
どっちも可
物を覚えない為にパソコン使ってるんだから順不同で書きたい
互換性とか気にしなくていいのでは?
良いアプリならユーザーが勝手にmarkdownとの変換ツール等を作ってくれる
どんどん分裂していこう
WiKi parserを簡単に作る
正規表現でやる
まじめに1文字ずつ読むようなparserとか書かない
React使うのでclient js側でparseする
処理はclientに分散するので実行速度は無視
サーバーでparseしない
どうやってもReactのrenderよりは軽いので気にしなくていいと思う
基本的な流れ
生テキストがある
今日は[builderscon]に行ったよ
1. 生テキストを正規表現Aでsplitする
記法部分と生テキストに別れる
今日は
と [builderscon]
と に行ったよ
になる
2. 記法部分から正規表現Bでmatchする
記法の中からパーツが取れる
[builderscon]
から builderscon
を取り出す
3. あとはtreeを返してReactでDOMにすればいい
つらみ
よく似た正規表現AとB、2つ書く必要がある
1つではどうしてもできない
その2つを同時に修正しないとparserが壊れる
このスライドでは簡単な正規表現で説明しているけど
実際は /\[([^\[\]]+)\]/
とかなので、2つあると間違える
よく似た正規表現
1. 生テキストから記法とそれ以外を分ける
js> "今日は[builderscon]に行ったよ"
.split(/(\[.+\])/)
[ '今日は', '[builderscon]', 'に行ったよ' ]
2. 記法の中からパーツを取り出す
js> "[builderscon]".match(/\[(.+)\]/)
[ '[builderscon]',
'builderscon', // パーツが取れた
index: 0,
input: '[builderscon]' ]
ここで重要なのは丸カッコ ( )
の位置で
1と2で正規表現の中にあったり外にあったりする
これを変換できれば正規表現1つで済むのでは?!
つまり
/\[(.+)\]/
と
/(\[.+\])/
を変換したい
解決方法
正規表現Cを宣言し、AとB2つの正規表現に変換する
flagsとsourceを使うと変形できるじゃん
js> var reg = /\[.+\]/mi
undefined
> reg.flags
'im'
> reg.source
'\\[.+\\]'
> new RegExp('(' + reg.source + ')', reg.flags)
/(\[.+\])/im // 前後に丸カッコ付けた新しい正規表現
capture (
と )
をどうにかする
/\[(.+)\]/
/(\[.+\])/
を変換したいが、丸カッコを単純に削除してしまうと壊れる正規表現がある
(a|b)
とか
丸カッコを無効化すると良さそうだ
(a|b)
を (?:a|b)
にする
?:
を付けると、グループ化はするけどmatchには出てこないようになる
/\[(.+)\]/
を
/\[(?:.+)\]/
にしてから、
/(\[(?:.+)\])/
にするといい
sourceを地道にreplaceしてnon-capturing groupsにした正規表現を返す
js// disable "capture" in RegExp
// replace (~~) with (?:~~)
module.exports = function (regexp) {
return regexp
.source
.replace(/\(\((?!\?)/g, function (leftParenthesis) {
return leftParenthesis + '?:'
})
.replace(/(^|[^\\])\((?!\?)/g, function (leftParenthesis) {
return leftParenthesis + '?:'
})
}
これで2つの正規表現が変換で作れるようになった
npmにした
最終型
ここから色々やると、1つの正規表現を書くだけでparserが作れるようになる
jsexport const parsePageLink = createNodeParser(/\[([^\[\]]+)\]/, ([, page]) => ({
type: 'pageLink',
page: title
}))
まとめ
正規表現1つ書けばよくなってメンテナンス性が上がった