You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.05.25 07:41:08