如何使LPeg.match返回nil。

我目前正在熟悉 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 -- 范围
    elseif not op and floor(version) == floor(a+0) then
        return a        -- 单个项目
    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 的调用都返回 nothing(意味着版本不匹配),grammar:match(...) 实际上将没有捕获,因此返回字符串的当前位置。但这正是我 不想 的,如果没有匹配,我希望 checkversion 返回 nilfalse,否则返回可以计算为 true 的值,实际上就像 string:match 一样。

另一方面,如果在不匹配的情况下从 check 返回 falsenil,我最终会得到来自匹配的返回值,如 nil,“1”,nil,nil,这基本上是无法处理的。

有什么想法吗?

点赞
用户68204
用户68204

将多返回的match处理起来并不是不可能的,只要你以一种简单的方式捕获它们。我添加了一个名为matched的函数来处理它们,并在你的check函数中添加了回退返回值为false

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 -- 范围
        elseif not op and floor(version) == floor(a+0) then
            return a        -- 单个项目
        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包裹起来,这样作为check函数的闭包上值的局部变量version将具有限制的作用域,并添加了一个参数到checkversion()函数以便于更清晰地运行少量测试用例。例如:

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       true
6.25    true
7.25    false
8       true
8.5     true
10      false

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

请注意,nilfalse在这种情况下都可以同样有效。它只是感觉更加合理,可以像普通的Lua数组一样处理收集到的列表,而不需要关注缺口。

2013-09-13 20:39:38
用户282536
用户282536

我认为你可以使用一个永远捕获 nil 的常量来给它添加 +

grammar = grammar + lpeg.Cc(nil)
2013-09-14 19:32:07
用户1244588
用户1244588

这是我最终使用的模式:

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),
});
2013-10-15 15:11:37