LPEG报‘Rule may be left-recursive’错误,但语法可终止的GLSL预处理器问题
解决LPEG中GLSL预处理器#ifdef的左递归错误
我之前也踩过PEG递归的坑,刚好能帮到你!
问题根源:间接左递归触发LPEG的检测机制
你写的语法逻辑上确实可终止,但LPEG基于PEG(Parsing Expression Grammar),PEG的核心特性是按顺序尝试匹配规则,且完全不支持左递归——哪怕是间接左递归。
你的规则链形成了循环依赖:S → ifdef → S。当LPEG尝试匹配S时,会优先尝试ifdef分支,而ifdef又需要匹配S,这会导致解析器无限递归调用S规则——哪怕后面有#endif作为终止条件,LPEG在匹配开始时无法预知后续内容,所以直接抛出Rule may be left-recursive错误。
解决办法:重构语法为右递归结构
要解决这个问题,我们需要把循环依赖的规则拆分成基础内容+递归嵌套块的结构,让递归方向是右递归(递归部分出现在规则末尾),同时利用LPEG的V(变量规则)延迟绑定递归规则,避免初始化时的循环引用。
下面是具体的重构思路和代码示例:
1. 拆分规则,明确层级
- 先定义基础代码块:匹配所有非预处理指令的GLSL代码
- 定义单个预处理指令:
#define、#undef、#ifdef块等 - 定义块内容:允许嵌套的代码+预处理指令组合(递归核心)
- 最后用顶层规则包裹整个文件内容
2. 示例代码
local lpeg = require 'lpeg' local P, S, V, C, Ct = lpeg.P, lpeg.S, lpeg.V, lpeg.C, lpeg.Ct -- 辅助规则:匹配任意空白字符(空格、制表符、换行) local whitespace = S" \t\n\r"^0 -- 辅助规则:匹配GLSL标识符(符合#define的变量名规则) local identifier = (P('_') + lpeg.R('az', 'AZ')) * (P('_') + lpeg.R('az', 'AZ', '09'))^0 -- 1. 基础规则:匹配非预处理指令的GLSL代码 -- 匹配到下一个#或者文件结束为止 local code = C((P(1) - P'#')^1) -- 2. 预处理指令规则:#define local define = P"#define" * whitespace * C(identifier) * whitespace * C((P(1) - S"\n")^0) * S"\n"^1 -- 预处理指令规则:#undef local undef = P"#undef" * whitespace * C(identifier) * S"\n"^1 -- 前向声明递归规则(LPEG需要先知道规则名,再定义) local block_content, ifdef_block, preproc -- 预处理指令规则:#ifdef块(支持嵌套) ifdef_block = P"#ifdef" * whitespace * C(identifier) * whitespace * V'block_content' -- 递归匹配块内容(代码+其他预处理指令) * P"#endif" * whitespace -- 所有预处理指令的集合 preproc = define + undef + ifdef_block -- 可在此添加#include规则 -- 3. 递归块内容:代码和预处理指令的任意组合(支持嵌套#ifdef) block_content = Ct((code + preproc)^0) -- 4. 顶层规则:整个GLSL文件的内容 local glsl_preprocessor = Ct(block_content)
3. 为什么这样能解决问题?
- 用
V'block_content'延迟绑定递归规则,避免了初始化时的循环引用 - 递归方向是右递归:
block_content先匹配code,再匹配preproc,ifdef_block的递归部分在规则末尾,LPEG可以匹配到#endif后终止递归,不会陷入无限循环 - 匹配顺序是“先代码,后预处理指令”,确保不会误把代码中的
#当成预处理指令的开头
额外注意事项
- 如果要处理
#ifndef、#else等扩展,只需在preproc规则中添加对应分支,逻辑和ifdef_block类似 - 匹配
code时可优化规则,比如排除GLSL字符串中的#(避免误触发预处理指令) - 用
Ct(捕获表)可将解析结果整理成结构化表,方便后续处理宏替换、条件编译逻辑
内容的提问来源于stack exchange,提问作者Proloe
相关产品推荐
相关产品推荐

