如何用Rust Syn crate解析proc macro时强制缩进规则?
问题
我正在使用Rust的syn crate编写名为block!的proc macro,用于解析类JSX/RSX的语法,目标是将指定代码解析为包含子块的结构。目前解析逻辑可正常运行,但无法强制实现父子块的缩进规则:嵌套子块必须以双空格或制表符开头。通过Token![<]、parse::<LitStr>()等方式解析TokenStream时无法满足规则,且TokenStream中未包含空白信息,查看syn文档未找到相关解析方法,请问该需求是否可行及如何实现?
解决方案
这个需求完全可行,核心思路是绕开syn默认丢弃空白符的TokenStream,从原始输入字符串入手处理缩进验证,步骤如下:
1. 获取proc macro的原始输入文本
proc macro的入口函数接收的TokenStream本身不含空白,但可以通过proc_macro::Span::source_text()方法获取输入的原始字符串(该方法要求Rust 1.59及以上版本)。示例代码:
#[proc_macro] pub fn block(input: TokenStream) -> TokenStream { // 获取原始输入字符串 let source = input.to_string(); // 更精准的Span获取方式(处理复杂输入场景) // let span = proc_macro::Span::call_site(); // let source = span.source_text().unwrap_or_else(|_| input.to_string()); // 后续缩进验证逻辑... }
2. 手动验证缩进规则
按行拆分原始字符串,逐行分析块的层级关系:
- 统一缩进单位:将所有制表符转换为双空格,避免混合缩进的判断混乱
- 维护当前层级的缩进长度:根层级缩进为0,每进入一个嵌套块,缩进长度增加2;退出块则减少2
- 识别块起始/结束行:以
<开头的行(对应类JSX块),提取前导空白长度,检查是否匹配当前层级要求 - 抛出错误:一旦发现不符合规则的缩进,立即返回编译错误,示例:
use regex::Regex; // 需要引入regex crate fn validate_indentation(source: &str) -> Result<(), syn::Error> { let re = Regex::new(r"^(\s*)").unwrap(); let mut current_indent = 0; let lines = source.lines().enumerate(); for (line_num, line) in lines { let trimmed_line = line.trim_start(); if trimmed_line.is_empty() || trimmed_line.starts_with("//") { continue; // 跳过空行和单行注释 } if trimmed_line.starts_with('<') { let caps = re.captures(line).unwrap(); let leading_whitespace = caps.get(1).map_or("", |m| m.as_str()); // 统一转换制表符为双空格 let normalized_whitespace = leading_whitespace.replace('\t', " "); let indent_len = normalized_whitespace.len(); if indent_len != current_indent { return Err(syn::Error::new( proc_macro2::Span::call_site(), format!("第{}行:嵌套块缩进不符合要求,需为{}个空格", line_num + 1, current_indent) )); } // 根据块结束标记更新层级(需匹配你的语法规则) if trimmed_line.starts_with("</") { current_indent = current_indent.saturating_sub(2); } else { current_indent += 2; } } } Ok(()) }
3. 验证通过后继续syn解析
缩进验证无误后,将原始字符串转换回TokenStream,继续原本的解析逻辑:
#[proc_macro] pub fn block(input: TokenStream) -> TokenStream { let source = input.to_string(); validate_indentation(&source).unwrap_or_else(|e| return e.to_compile_error().into()); // 转换回TokenStream并执行原有解析逻辑 let parsed_input = syn::parse_str::<YourCustomStruct>(&source).unwrap(); // 后续代码生成逻辑... }
额外注意事项
- 处理跨多行的块内容:需准确识别块的起始和结束标记,避免错误更新层级
- 支持多行注释:可扩展逻辑跳过
/* */包裹的注释内容 - 封装验证逻辑:将缩进检查代码抽离为独立模块,提升代码可维护性
内容的提问来源于stack exchange,提问作者Slava.In
相关产品推荐
相关产品推荐

