LPEG将含lpeg.Cmt的循环体判定为空的问题及解决办法咨询
问题描述
在Lua 5.1、5.2、5.3、5.4及LuaJIT环境中,当使用lpeg.Cmt(…)组成循环体时,LPEG的静态分析会判定该循环体“可能匹配空字符串”(报错loop body may accept empty string),但实际上传入lpeg.Cmt的函数要么返回前进后的位置(消耗输入),要么返回false表示匹配失败,绝不会匹配空串。
最简报错示例:
local lpeg = require 'lpeg' local pattern = lpeg.Cmt (lpeg.P (true), function (s, p) local n = f (s, p) -- f的结果无法提前计算 if not ((n or 0) > 0) then return false -- 无法前进则匹配失败 else return p + n, string.sub (s, p, p + n - 1) -- 修正原代码的字符串截取参数 end end) ^ 1
解决办法
LPEG的静态检查无法感知lpeg.Cmt内部函数的逻辑,因此需要显式让循环体模式确保至少消耗一个字符,以此绕过静态检查。
方法一:前置强制匹配单个字符
在lpeg.Cmt前添加lpeg.P(1)(匹配任意单个字符),让LPEG明确知道循环体每次执行至少会消耗一个字符,从而避免空循环的判定。同时需要调整Cmt函数内的位置计算:
local lpeg = require 'lpeg' local pattern = (lpeg.P(1) * lpeg.Cmt(lpeg.Cc(true), function(s, p, _) -- 前置的lpeg.P(1)已让位置p前进1,传入f的起始位置需回退1位 local n = f(s, p-1) if not (n and n > 0) then return false end -- 已消耗1个字符,剩余n-1个字符需要前进 return p + (n - 1), string.sub(s, p-1, p-1 + n - 1) end)) ^ 1
方法二:用递归规则替代循环运算符
如果不想修改原模式的位置逻辑,可以使用LPEG的递归语法(lpeg.V)构建循环,这种方式下静态分析不会触发空循环报错:
local lpeg = require 'lpeg' local grammar = lpeg.P { "LOOP", LOOP = lpeg.Cmt(lpeg.P(true), function(s, p) local n = f(s, p) if not (n and n > 0) then return false end local new_p = p + n -- 匹配当前片段后,递归尝试继续匹配 return new_p, string.sub(s, p, new_p - 1) * lpeg.V("LOOP") end) + lpeg.P(true) -- 终止条件:无法匹配时结束循环 } local pattern = grammar
补充说明
原示例中的string.sub(s, p, n)存在参数错误,string.sub的第三个参数是结束位置而非长度,正确写法应为string.sub(s, p, p + n - 1),否则会截取错误的字符串片段。
内容的提问来源于stack exchange,提问作者Alexander Mashin
相关产品推荐
相关产品推荐

