Как заставить LPeg.match вернуть ноль

В настоящее время я знакомлюсь с модулем парсера LPeg. Для этого я хочу соответствовать строке версии (например, 11.4) против списка.

Такой список представляет собой строку с жестким синтаксисом, которая также может содержать диапазоны. Вот 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 возвращать nil или же false если нет совпадения и что-то, что оценивается как истинное в противном случае, на самом деле просто как string:match сделал бы.

Если я с другой стороны вернусь false или же nil от check в случае несоответствия я получаю возвращаемые значения из соответствия, как nil, "1", nil, nil с которым в принципе невозможно справиться.

Есть идеи?

3 ответа

Решение

Это шаблон, который я в конечном итоге использовал:

nil_capturing_pattern * lpeg.Cc(nil)

Я включил его в грамматику в S правило (обратите внимание, что это также включает измененную грамматику для "правильного" определения порядка версий, поскольку в нумерации версий "4.7" <"4.11" является истинным, но не в исчислении)

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),
});

Я думаю, что вы можете или + это с постоянным захватом ноль:

grammar = grammar + lpeg.Cc(nil)

Несколько возвратов из match с ними невозможно справиться, если вы поймаете их так, чтобы облегчить их обработку. Я добавил функцию matched это делает это, и добавил запасной возврат false на ваш check,

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 ... end так что местный version который используется здесь в качестве повышения стоимости check ограничил бы область, и добавил параметр checversion() чтобы было понятнее проходить несколько тестовых случаев. Например:

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 или же false будет работать одинаково хорошо в этом случае. Просто кажется более разумным собрать список, который можно обрабатывать как обычную таблицу, похожую на массив Lua, не обращая внимания на дыры.

Другие вопросы по тегам