Rust中使用宏创建领域特定语言 (DSL)


如果DSL 需要嵌入 Rust 代码中(如内联汇编或 SQL 语句),您应该使用过程宏。
相反,如果 DSL 代码是要解释的单独文件,则可以使用解析包,例如 NomPest

也可以使用声明性宏。
这段代码是带有整个 html/css 语言,并且它是完全类型安全的,并且带有声明性宏。

对于语义分析器,请使用eggegglog
它们是自定义数据结构,用于以非破坏性方式表示编译器重写规则。
传统的编译器优化器的工作方式是与输入的模式相匹配,并对其进行破坏性的改写,但这意味着改写的顺序非常重要。
例如,如果你在改写(x * a) / a = x之前应用改写x * 2 = x << 2,你就会失去更好的优化机会,因为(x * 2) / 2会被改写成(x << 2) / 2,这不会被第二个规则所匹配。
egg代替了对等价词组的跟踪,并以一种紧凑的方式存储它们。你可以得到一个单一的节点,它指向所有已知的计算表达式,然后你可以相当有效地与之进行模式匹配,并且可以进行应用分析和成本函数,为你的程序非常有效地搜索广泛的可能形式。

还有直接使用基于纯文本的逐字符进行扫描,其中涉及了分词器,有一些超级简单且可读的东西,如下所示:

fn scan_rust_line_comment(&mut self) -> ScannerResult<'text, &'text str> {
    self.scan_with(|scanner| {
        scanner.accept_str("//")?;
        scanner.skip_until_char_any(&['\n', '\r']);
        Ok(())
    })
}

fn scan_rust_identifier(&mut self) -> ScannerResult<'text, &'text str> {
    self.scan_with(|scanner| {
        scanner.accept_if(|c| c.is_alphabetic() || (c == '_'))?;
        scanner.skip_while(|c| c.is_alphanumeric() || (c == '_'));
        Ok(())
    })
}

fn scan_rust_raw_identifier(&mut self) -> ScannerResult<'text, &'text str> {
    self.scan_with(|scanner| {
        scanner.accept_str(
"r#")?;
        scanner.scan_rust_identifier()?;
        Ok(())
    })
}

fn scan_rust_keyword(&mut self) -> ScannerResult<'text, &'text str> {
    self.scan_with(|scanner| {
        let (r, ident) = scanner.scan_rust_identifier()?;
        if KEYWORDS.contains(&ident) {
            Ok(())
        } else {
            Err((r, ident))
        }
    })
}

其中KEYWORDS是["as", "break", ...].

总结
宏用于在编译期间创建 Rust 代码。

好的宏应该尽可能坚持 Rust 代码。

您可以将 syn 用于过程宏。

用于创建用于其他目的的扫描器和解析器的工具包括:

  • logos (基于结构的扫描仪生成器)
  • pest(PEG 解析器生成器)
  • nom(解析器组合器库)
  • lalrpop(用于构建 LR(1) 解析器的解析器生成器)
  • rowan(管理 CST 的库)