技術戦略室間野です。好きなうまるちゃんは家うまるです。どなたか次回までにアフィリエイトどこに貼れば良いか教えてください。

これイマイチSEOスコアが低いので他の話をする予定でしたが、今まさにparser書いたのと、概ね全言語にPEGモジュールがあったので考え直しました。100行のPEGは100行のRegExよりだいぶ読める。

PEGとは

Perl6のsyntax parserです。

正規表現の代用

PEGで出来ることの99%は正規表現でも出来て、正規表現で出来ることの(略)PEGの実装は小さく、大抵Cのラッパなので速度もほぼ変わりませんが、どちらが上とかの話はしません。

ただしLuaおめえはダメだ

Luaの正規表現は特殊で、{}や|が無いせいで、ロジックまで変わってしまい、PCREに移植不能になることがあります。PEGならどこにでも持って行けるので、LuaだけはPEGへの全置換を検討しても良いでしょう。

Perlは全域で正規表現を一切使わなければバイナリが小さくなるので、Luaも速度的恩恵があるかも。ないかも。

ini parser

要lpeg

sudo luarocks install lpeg

実装


function toLines(str)
    local lines = {}
    for val in str:gmatch("(.-)\r?\n") do
        table.insert(lines, val)
    end
    return lines
end

function decodeIni(str)
    local lpeg = require "lpeg"
    local sp = lpeg.P(" ") ^ 0 --0文字以上の空白
    local bb = lpeg.P("[")      -- [
    local eb = lpeg.P("]")      -- ]
    local eq = lpeg.P("=")      -- =
    local cr = lpeg.P("\r")     -- <CR>
    local lf = lpeg.P("\n")     -- <LF>
    local key   = (lpeg.P(1) - lpeg.P(" ") - eq) ^1
    local value = lpeg.P(1) ^0

    local ptSubject = sp * bb * lpeg.C((lpeg.P(1) - eb) ^1) * eb
    local ptBody    = sp * lpeg.C(key) * sp * eq * sp * lpeg.C(value)

    local struct = {}
    local parentName = nil
    local lines = toLines(str)
    local matched
    for i,line in ipairs(lines) do
        matched = lpeg.match(ptSubject, line)
        if matched then
            parentName = matched
            if not struct[parentName] then struct[parentName] = {} end
        else
            ckey,val = lpeg.match(ptBody, line)
            if ckey then
                if parentName then
                    struct[parentName][ckey] = val
                else
                    struct[ckey] = val
                end
            end

        end
    end
    return struct
end

decodeIniに下のようなString型を食わせるとテーブルを返します。


PARENTNAME=parent

[api]
hostname = 192.168.1.111
port = 8080
authUri = /auth

[auth]
username = root
passwprd = rpwd

簡単な解説

リンク張って済ませるとこですが、イマイチ実装向きの解説がないので、PEGについて超簡単に解説します。

意味 正規表現
* 連結
+ または []
- 除外 [^]
^ 連続 {}

今回使ったのはこのぐらい。
(lpeg.P(1) - eb) ^1
の意味は以下です。
任意の1文字 ただし]以外 の1文字以上の連続

先頭から探索

正規表現で
"abc" =~ /b/
はマッチしますが、PEGでは以下のように解釈されます。
"abc" =~ /^b/

日本語(UTF8)

lpegでは日本語は単なる3文字(一部4文字)と見なされます。即ちlpeg.P(3)または(4)で日本語1文字です。

再利用性

PEGではなるべく再利用可能な記述でパターンを組み立てるのがスタンダードです。PCREで <h1>foo</h1> のfooを取り出す時
<h1>(.+?)</h1>
などとキャプチャしがちですが、PEGでは以下のようにタグを組み立てた方が後々幸せです。
lt = p("<")
gt = p(">")
h1 = p("h1")
openh1 = lt * h1 * gt
...

Ce8e5e816e2a21827eb04022ac5a29f2
へろー ヽ( ´ ー ` )ノ