Rust语法解析库cfgrammar的使用,cfgrammar提供高效的上下文无关文法解析与词法分析工具

cfgrammar 读取语法文件,处理它们,并提供一个方便的 API 来操作它们。它可能对那些直接操作语法或希望使用自定义类型解析器的人感兴趣。

以下是一个完整的示例代码,展示如何使用 cfgrammar 进行上下文无关文法解析与词法分析:

use cfgrammar::yacc::YaccKind;
use cfgrammar::{Grammar, Symbol};
use lrlex::DefaultLexerTypes;

fn main() {
    // 定义语法规则
    let grammar = r#"
        %start Expr
        %%
        Expr: Expr '+' Term | Term;
        Term: Term '*' Factor | Factor;
        Factor: 'id' | '(' Expr ')';
    "#;

    // 解析语法并构建 Grammar 对象
    let grm = Grammar::new(YaccKind::Original, grammar).unwrap();

    // 定义词法分析器
    let lexer = |input: &str| -> Vec<lrlex::LexToken<DefaultLexerTypes>> {
        let mut tokens = Vec::new();
        let mut chars = input.chars().peekable();
        let mut pos = 0;

        while let Some(c) = chars.next() {
            match c {
                ' ' | '\t' | '\n' => { pos += 1; } // 跳过空白字符
                '+' => {
                    tokens.push(lrlex::LexToken::new(DefaultLexerTypes::PLUS, pos, pos+1));
                    pos += 1;
                }
                '*' => {
                    tokens.push(lrlex::LexToken::new(DefaultLexerTypes::STAR, pos, pos+1));
                    pos += 1;
                }
                '(' => {
                    tokens.push(lrlex::LexToken::new(DefaultLexerTypes::LPAREN, pos, pos+1));
                    pos += 1;
                }
                ')' => {
                    tokens.push(lrlex::LexToken::new(DefaultLexerTypes::RPAREN, pos, pos+1));
                    pos += 1;
                }
                'a'..='z' | 'A'..='Z' => {
                    let start = pos;
                    while let Some(&nc) = chars.peek() {
                        if nc.is_alphabetic() {
                            chars.next();
                            pos += 1;
                        } else {
                            break;
                        }
                    }
                    tokens.push(lrlex::LexToken::new(DefaultLexerTypes::ID, start, pos+1));
                    pos += 1;
                }
                _ => panic!("Unexpected character: {}", c),
            }
        }
        tokens
    };

    // 输入字符串
    let input = "id + id * id";

    // 进行词法分析
    let tokens = lexer(input);

    // 使用 Grammar 对象进行解析
    let parser = lrpar::LRParser::new(&grm);
    let result = parser.parse(&tokens);

    match result {
        Ok(ast) => {
            println!("Parse successful: {:?}", ast);
        }
        Err(e) => {
            println!("Parse error: {:?}", e);
        }
    }
}

以下是一个完整的示例代码,展示如何使用 cfgrammar 进行上下文无关文法解析与词法分析:

use cfgrammar::yacc::YaccKind;
use cfgrammar::{Grammar, Symbol};
use lrlex::DefaultLexerTypes;
use lrpar::LRParser;

fn main() {
    // 定义语法规则字符串
    let grammar = r#"
        %start Expr
        %%
        Expr: Expr '+' Term 
            | Term;
        Term: Term '*' Factor 
            | Factor;
        Factor: 'id' 
            | '(' Expr ')';
    "#;

    // 解析语法并构建 Grammar 对象,使用 Original Yacc 类型
    let grm = Grammar::new(YaccKind::Original, grammar).unwrap();

    // 定义词法分析器闭包函数
    let lexer = |input: &str| -> Vec<lrlex::LexToken<DefaultLexerTypes>> {
        let mut tokens = Vec::new(); // 存储词法标记的向量
        let mut chars = input.chars().peekable(); // 可窥视的字符迭代器
        let mut pos = 0; // 当前位置

        // 遍历输入字符串的所有字符
        while let Some(c) = chars.next() {
            match c {
                // 跳过空白字符(空格、制表符、换行符)
                ' ' | '\t' | '\n' => { 
                    pos += 1; 
                }
                // 处理加号运算符
                '+' => {
                    tokens.push(lrlex::LexToken::new(DefaultLexerTypes::PLUS, pos, pos+1));
                    pos += 1;
                }
                // 处理乘号运算符
                '*' => {
                    tokens.push(lrlex::LexToken::new(DefaultLexerTypes::STAR, pos, pos+1));
                    pos += 1;
                }
                // 处理左括号
                '(' => {
                    tokens.push(lrlex::LexToken::new(DefaultLexerTypes::LPAREN, pos, pos+1));
                    pos += 1;
                }
                // 处理右括号
                ')' => {
                    tokens.push(lrlex::LexToken::new(DefaultLexerTypes::RPAREN, pos, pos+1));
                    pos += 1;
                }
                // 处理标识符(字母字符)
                'a'..='z' | 'A'..='Z' => {
                    let start = pos; // 记录标识符开始位置
                    // 连续读取字母字符直到非字母字符
                    while let Some(&nc) = chars.peek() {
                        if nc.is_alphabetic() {
                            chars.next();
                            pos += 1;
                        } else {
                            break;
                        }
                    }
                    // 添加标识符标记
                    tokens.push(lrlex::LexToken::new(DefaultLexerTypes::ID, start, pos+1));
                    pos += 1;
                }
                // 处理意外字符
                _ => panic!("Unexpected character: {}", c),
            }
        }
        tokens // 返回词法标记向量
    };

    // 定义输入字符串
    let input = "id + id * id";

    // 执行词法分析,将输入字符串转换为词法标记
    let tokens = lexer(input);

    // 创建 LALR 解析器实例
    let parser = LRParser::new(&grm);
    
    // 使用解析器解析词法标记
    let result = parser.parse(&tokens);

    // 处理解析结果
    match result {
        // 解析成功,输出抽象语法树
        Ok(ast) => {
            println!("Parse successful: {:?}", ast);
        }
        // 解析失败,输出错误信息
        Err(e) => {
            println!("Parse error: {:?}", e);
        }
    }
}

1 回复

Rust语法解析库cfgrammar的使用指南

简介

cfgrammar是一个高效的Rust上下文无关文法解析与词法分析工具库。它提供了强大的语法解析能力,支持构建自定义语法分析器,特别适合编译器开发、领域特定语言(DSL)实现和复杂文本处理任务。

主要特性

  • 支持LR(1)和LALR(1)解析算法
  • 自动生成词法分析器
  • 类型安全的语法规则定义
  • 高性能的解析执行
  • 详细的错误报告和诊断信息

安装方法

在Cargo.tml中添加依赖:

[dependencies]
cfgrammar = "0.9"
cfgrammar-yacc = "0.9"

基本使用方法

1. 定义语法规则

创建YACC格式的语法文件(例如:calculator.y):

%start Expr
%%
Expr: Term | Expr '+' Term | Expr '-' Term;
Term: Factor | Term '*' Factor | Term '/' Factor;
Factor: NUMBER | '(' Expr ')';
%%

2. 配置构建脚本

在build.rs中:

use cfgrammar::yacc::YaccKind;
use cfgrammar_yacc::YaccParser;

fn main() {
    YaccParser::new()
        .kind(YaccKind::Grmtools)
        .grammar_in_src_dir("calculator.y")
        .unwrap()
        .generate()
        .unwrap();
}

3. 实现词法分析器

use std::str::FromStr;
use cfgrammar::Span;
use lrlex::DefaultLexerTypes;

#[derive(Debug)]
pub enum LexicalError {
    InvalidToken(Span),
}

fn lexer(input: &str) -> Result<Vec<(Span, DefaultLexerTypes)>, LexicalError> {
    let mut tokens = Vec::new();
    let mut chars = input.char_indices().peekable();
    
    while let Some((start, c)) = chars.next() {
        match c {
            '+' | '-' | '*' | '/' | '(' | ')' => {
                tokens.push((Span::new(start, start + 1), DefaultLexerTypes::from_str(&c.to_string()).unwrap()));
            }
            '0'..='9' => {
                let mut end = start + 1;
                while let Some((pos, c)) = chars.peek() {
                    if c.is_ascii_digit() {
                        end = *pos + 1;
                        chars.next();
                    } else {
                        break;
                    }
                }
                tokens.push((Span::new(start, end), DefaultLexerTypes::NUMBER));
            }
            ' ' | '\t' | '\n' => continue,
            _ => return Err(LexicalError::InvalidToken(Span::new(start, start + 1))),
        }
    }
    Ok(tokens)
}

4. 使用解析器

mod calculator;
use calculator::YaccParser;

fn main() -> Result<(), Box<dyn std::error::Error>> {
    let input = "2 + 3 * (4 - 1)";
    let lexer_result = lexer(input)?;
    let parser = YaccParser::new();
    let result = parser.parse(&lexer_result);
    
    match result {
        Ok(ast) => println!("解析结果: {:?}", ast),
        Err(e) => println!("解析错误: {:?}", e),
    }
    
    Ok(())
}

高级功能示例

自定义动作处理

%start Expr
%%
Expr -> Term: $1
    | Expr '+' Term: $1 + $3
    | Expr '-' Term: $1 - $3;
    
Term -> Factor: $1
    | Term '*' Factor: $1 * $3
    | Term '/' Factor: $1 / $3;
    
Factor -> NUMBER: $1
    | '(' Expr ')': $2;
%%

错误恢复和处理

use cfgrammar::Recovery;

let parser = YaccParser::new()
    .recovery(Recovery::Enabled)
    .error_recovery(true);

let result = parser.parse_with_recovery(tokens);

完整示例demo

以下是一个完整的计算器示例,包含语法定义、词法分析和解析执行:

Cargo.toml:

[package]
name = "calculator"
version = "0.1.0"
edition = "2021"

[dependencies]
cfgrammar = "0.9"
cfgrammar-yacc = "0.9"
lrlex = "0.9"

src/calculator.y:

%start Expr
%%
// 表达式语法规则
Expr: Term          { $1 }
    | Expr '+' Term { $1 + $3 }
    | Expr '-' Term { $1 - $3 };
    
// 项语法规则
Term: Factor          { $1 }
    | Term '*' Factor { $1 * $3 }
    | Term '/' Factor { $1 / $3 };
    
// 因子语法规则
Factor: NUMBER        { $1 }
      | '(' Expr ')'  { $2 };
%%

build.rs:

use cfgrammar::yacc::YaccKind;
use cfgrammar_yacc::YaccParser;

fn main() {
    // 配置YACC解析器生成
    YaccParser::new()
        .kind(YaccKind::Grmtools)
        .grammar_in_src_dir("calculator.y")
        .unwrap()
        .generate()
        .unwrap();
}

src/main.rs:

mod calculator;
use calculator::YaccParser;
use cfgrammar::Span;
use lrlex::DefaultLexerTypes;
use std::str::FromStr;

// 词法错误类型
#[derive(Debug)]
pub enum LexicalError {
    InvalidToken(Span),
}

// 词法分析器实现
fn lexer(input: &str) -> Result<Vec<(Span, DefaultLexerTypes)>, LexicalError> {
    let mut tokens = Vec::new();
    let mut chars = input.char_indices().peekable();
    
    while let Some((start, c)) = chars.next() {
        match c {
            // 处理运算符和括号
            '+' | '-' | '*' | '/' | '(' | ')' => {
                tokens.push((Span::new(start, start + 1), 
                           DefaultLexerTypes::from_str(&c.to_string()).unwrap()));
            }
            // 处理数字
            '0'..='9' => {
                let mut end = start + 1;
                while let Some((pos, c)) = chars.peek() {
                    if c.is_ascii_digit() {
                        end = *pos + 1;
                        chars.next();
                    } else {
                        break;
                    }
                }
                tokens.push((Span::new(start, end), DefaultLexerTypes::NUMBER));
            }
            // 跳过空白字符
            ' ' | '\t' | '\n' => continue,
            // 处理无效字符
            _ => return Err(LexicalError::InvalidToken(Span::new(start, start + 1))),
        }
    }
    Ok(tokens)
}

fn main() -> Result<(), Box<dyn std::error::Error>> {
    // 测试输入表达式
    let input = "2 + 3 * (4 - 1)";
    println!("输入表达式: {}", input);
    
    // 执行词法分析
    let lexer_result = lexer(input)?;
    
    // 创建解析器并执行语法分析
    let parser = YaccParser::new();
    let result = parser.parse(&lexer_result);
    
    // 处理解析结果
    match result {
        Ok(ast) => println!("解析结果: {}", ast),
        Err(e) => println!("解析错误: {:?}", e),
    }
    
    Ok(())
}

最佳实践

  1. 为复杂的语法规则编写详细的测试用例
  2. 使用Span信息进行精确的错误定位
  3. 合理设计语法规则避免歧义
  4. 利用cfgrammar提供的诊断工具进行调试

性能优化建议

  • 尽可能使用LALR(1)而不是LR(1)
  • 避免过度复杂的语法规则
  • 使用合适的缓存策略
  • 合理处理词法分析中的空白字符

这个库为Rust开发者提供了强大的语法解析能力,特别适合需要自定义语法处理的应用程序开发。

回到顶部