3

現在、LPeg パーサー モジュールに慣れてきました。このために、バージョン文字列 (例: 11.4) をlistと照合します。

このようなリストは、範囲を含むこともできるタイトな構文の文字列です。これは EBNF に似ていますが、いずれにしても非常に単純な文法です (以下の LPeg コードは少し読みにくいので書き留めておきます)。

S = R, { ',', R }
R = N, [ '-', N ]
N = digit+, [ '.', digit+ ]

文字列の例は1-9,10.1-11,12. これが私の巨大なコードです:

local L = require "lpeg"
local LV, LP, LC, LR, floor = L.V, L.P, L.C, L.R, math.floor
local version = "7.25"

local function check(a, op, b)
    if op and a+0 <= version and version <= b+0 then
        return a..op..b -- range
    elseif not op and floor(version) == floor(a+0) then
        return a        -- single item
    end
end
local grammar = LP({ "S",
    S = LV"R" * (LP"," * LV"R")^0,
    R = LV"V" * (LC(LP"-") * LV"V")^-1 / check,
    V = LC(LV"D" * (LP"." * LV"D")^-1),
    D = (LR("09")^1),
})
function checkversion(str)
    return grammar:match(str)
end

したがって、次のように呼び出しcheckversion("1-7,8.1,8.3,9")、現在のバージョンがリストと一致しない場合は、取得する必要nilがあります。

問題は、すべての呼び出しがcheck何も返さない場合 (つまり、バージョンが一致しない場合)、grammar:match(...)実際にはキャプチャがないため、文字列の現在の位置が返されることです。しかし、これはまさに私が望んでいないcheckversionことです。戻りたいnilfalse、一致するものがなく、それ以外の場合は true と評価されるものがあれば、実際にstring:matchはそうします。

一方、不一致の場合に returnfalseまたはnilfromの場合、基本的に処理できないcheckmatch like からの戻り値になります。nil, "1", nil, nil

何か案は?

4

3 に答える 3

1

これは私が最終的に使用したパターンです:

nil_capturing_pattern * lpeg.Cc(nil)

私はそれをルールの文法に組み込みましたS(バージョンの番号付けでは "4.7" < "4.11" は true ですが、微積分ではそうではないため、これにはバージョンの順序を "正しく" 決定するための変更された文法も含まれることに注意してください)

local Minor_mag = log10(Minor);
local function check(a, am, op, b, bm)
    if op then
        local mag = floor(max(log10(am), log10(bm), Minor_mag, 1))+1;
        local a, b, v = a*10^mag+am, b*10^mag+bm, Major*10^mag+Minor;

        if a <= v and v <= b then
            return a..op..b;
        end
    elseif a == Major and (am == "0" or am == Minor) then
        return a.."."..am;
    end
end

local R, V, C, Cc = lpeg.R, lpeg.V, lpeg.C, lpeg.Cc
local g = lpeg.P({ "S",
    S = V("R") * ("," * V("R"))^0 * Cc(nil), 
    R = (V("Vm") + V("VM")) * (C("-") * (V("Vm") + V("VM")))^-1 / check,
    VM = V("D") * Cc("0"),
    Vm = V("D") * "." * V("D"),
    D = C(R("09")^1),
});
于 2013-10-15T15:11:37.667 に答える
1

+nilを常にキャプチャすることで、またはそれができると思います:

grammar = grammar + lpeg.Cc(nil)
于 2013-09-14T19:32:07.203 に答える
0

からの複数の返品はmatch、処理しやすい方法でキャッチすれば、処理することが不可能ではありません。それを行う関数matchedを追加し、 のフォールバック リターンを に追加しましfalsecheck

do
    local L = require "lpeg"
    local LV, LP, LC, LR, floor = L.V, L.P, L.C, L.R, math.floor
    local version = 6.25

    local function check(a, op, b)
        if op and a+0 <= version and version <= b+0 then
            return a..op..b -- range
        elseif not op and floor(version) == floor(a+0) then
            return a        -- single item
        end
        return false
    end
    local grammar = LP({ "S",
        S = LV"R" * (LP"," * LV"R")^0,
        R = LV"V" * (LC(LP"-") * LV"V")^-1 / check,
        V = LC(LV"D" * (LP"." * LV"D")^-1),
        D = (LR("09")^1),
    })

    local function matched(...)
        local n = select('#',...)
        if n == 0 then return false end
        for i=1,n do
            if select(i,...) then return true end
        end
        return false
    end

    function checkversion(ver,str)
        version = ver
        return matched(grammar:match(str))
    end
end

ここで上位値として使用されるdo ... endlocal がスコープを制限するように全体を囲み、パラメーターを追加して、いくつかのテストケースをより明確に実行できるようにしました。例えば:versioncheckchecversion()

cases = { 1, 6.25, 7.25, 8, 8.5, 10 }
for _,v in ipairs(cases) do
    print(v, checkversion(v, "1-7,8.1,8.3,9"))
end

実行すると、次のようになります。

C:\Users\Ross\Documents\tmp\SOQuestions>q18793493.lua
1 真
6.25 真
7.25 偽
8 真
8.5 真
10 偽

C:\Users\Ross\Documents\tmp\SOQuestions>

nilこの場合、 orのどちらでもfalse同じように機能することに注意してください。穴を気にせずに通常の Lua 配列のようなテーブルとして扱えるリストを集めた方が正気です。

于 2013-09-13T20:39:38.517 に答える