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

如何用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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.11 15:22:38