如何在LPeg模式中更改捕获结果的返回顺序?

假设我有一个模式 P,它可能会产生一些确定数量的捕获(如果有的话),并且我想编写一个模式 Q,它会捕获 P 以及 P 后面的位置——但是要求该位置在 P 的捕获之前返回。实际上,如果 lpeg.match(P * lpeg.Cp(), str, i) 的结果是 v1, v2, ..., j,那么我希望 lpeg.match(Q, str, i) 的结果是 j, v1, v2, ...

这是否可以在不必每次匹配 P 时创建新表的情况下实现?

大多数情况下,我想这样做可以简化一些生成迭代器的函数。Lua 的 有状态的迭代器函数 只有一个控制变量,并且它必须是迭代器函数返回的第一个值。

在一个允许人们为可变函数的 最后 参数命名的世界中,我可以编写:

function pos_then_captures(pattern)
    local function roll(..., pos)
        return pos, (...)
    end
    return (pattern * lpeg.Cp()) / roll
end

但是现实很残酷。一种简单的解决方法是明智地使用 lpeg.Ct()

function pos_then_captures(pattern)
    -- exchange the order of two values and unpack the first parameter
    local function exch(a, b)
        return b, unpack(a)
    end
    return (lpeg.Ct(pattern) * lpeg.Cp()) / exch
end

或者让调用方对 lpeg.match 进行打包/移除/插入/拆包操作。尽管后者听起来有些极端,但我可能会选择这种方法,因为 lpeg.Ct() 对于某些正常但类型异常的 pos_then_captures 参数可能会产生意外后果。

这两种方法中的任何一种都会在成功匹配 pattern 时创建一个新表,尽管在我的应用程序中这并不太重要,但是否有一种方法可以在不使用任何打包/拆包魔法的情况下实现此目标?

我对 Lua 的内部机制不太熟悉,但我感觉我真正想做的是从 Lua 的堆栈中弹出某些东西,然后将其放回到其他地方,这似乎不是一种直接或高效支持的操作,但也许在这种特定情况下 LPeg 可以做到。

点赞
用户3685847
用户3685847

匹配时间捕获和上值完成了任务。此函数使用 Cmt 确保在将其放入 pattern / prepend 的捕获前,pos 已设置。

Cmt = lpeg.Cmt
Cp  = lpeg.Cp

function prepend_final_pos(pattern)
    -- 上值是动态的,因此此变量属于每次调用 prepend_final_pos 时的新环境。
    local pos

    -- lpeg.Cmt(patt, func) 将整个被搜索的文本传递给 `func` 作为第一个参数,然后传递任何捕获值。忽略第一个参数。
    local function setpos(_, x)
      pos = x

      -- 如果我们没有返回任何内容,Cmt 将会每次都失败
      return true
    end

    -- 别让 varargs 受到影响!
    local function prepend(...)
      return pos, ...
    end

    -- `Cmt(etc etc) / 0` 中的 `/ 0` 是为了摆脱从 setpos 中获取的那个捕获到的 `true`。
    return (pattern / prepend) * (Cmt(Cp(), setpos) / 0)
end

示例会话:

> bar = lpeg.C "bar"
> Pbar = prepend_final_pos(bar)
> print(lpeg.match(Pbar, "foobarzok", 4))
7       bar
> foo = lpeg.C "foo" / "zokzokzok"
> Pfoobar = prepend_final_pos(foo * bar)
> print(lpeg.match(Pfoobar, "foobarzok"))
7       zokzokzok       bar

按照预期,实际捕获对新模式返回的位置没有影响;只有原始模式匹配的文本长度才会受到影响。

2015-07-02 02:34:05
用户40691
用户40691

你可以使用原始解决方案而不使用表捕获或匹配时间捕获,如下所示:

function pos_then_captures(pattern)
    local function exch(a1, a2, a3, a4, a5, a6, a7, a8, a9, a10, ...)
        if a1 == nil then return end
        if a2 == nil then return a1 end
        if a3 == nil then return a2, a1 end
        if a4 == nil then return a3, a1, a2 end
        if a5 == nil then return a4, a1, a2, a3 end
        if a6 == nil then return a5, a1, a2, a3, a4 end
        if a7 == nil then return a6, a1, a2, a3, a4, a5 end
        if a8 == nil then return a7, a1, a2, a3, a4, a5, a6 end
        if a9 == nil then return a8, a1, a2, a3, a4, a5, a6, a7 end
        if a10 == nil then return a9, a1, a2, a3, a4, a5, a6, a7, a8 end
        local t = { a10, ... }
        return t[#t], a1, a2, a3, a4, a5, a6, a7, a8, a9, unpack(t, 1, #t-1)
    end
    return (pattern * lpeg.Cp()) / exch
end

以下示例使用返回每个匹配的 'a' 以及其前面的匹配结束:

local p = lpeg.P{ (pos_then_captures(lpeg.C'a') + 1) * lpeg.V(1) + -1 }
print(p:match('abababcd'))

-- output: 2       a       4       a       6       a
2015-12-27 15:56:23