I'm currently getting familiar with the LPeg parser module. For this I want to match a version string (e.g. 11.4
) against a list.
Such a list is a string with a tight syntax that can also contain ranges. Here is an EBNF-like, but in any case quite simple grammar (I write it down because LPeg code below can be a bit difficult to read):
S = R, { ',', R }
R = N, [ '-', N ]
N = digit+, [ '.', digit+ ]
An example string would be 1-9,10.1-11,12
. Here is my enormous code:
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
So you would call it like checkversion("1-7,8.1,8.3,9")
and if the current version is not matched by the list you should get nil
.
Now, the trouble is, if all calls to check
return nothing (meaning, if the versions do not match), grammar:match(...)
will actually have no captures and so return the current position of the string. But this is exactly what I do not want, I want checkversion
to return nil
or false
if there is no match and something that evaluates to true otherwise, actually just like string:match
would do.
If I on the other hand return false
or nil
from check
in case of a non-match, I end up with return values from match like nil, "1", nil, nil
which is basically impossible to handle.
Any ideas?
This is the pattern I eventually used:
I incorporated it into the grammar in the
S
rule (Note that this also includes changed grammar to "correctly" determine version order, since in version numbering "4.7" < "4.11" is true, but not in calculus)