Как заставить 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, не обращая внимания на дыры.