Rust语法解析与词法分析库lrpar的使用,lrpar实现高效LR(1)解析器生成和自定义语法规则处理

Rust语法解析与词法分析库lrpar的使用,lrpar实现高效LR(1)解析器生成和自定义语法规则处理

lrpar提供了一个Yacc兼容的解析器(可以在编译时或运行时生成语法)。它可以接收传统的.y文件并将它们转换为符合Rust习惯的解析器。

示例

假设我们要为一个简单的计算器语言静态生成一个解析器(并假设我们可以使用lrlex作为词法分析器)。我们需要在项目中添加一个build.rs文件来静态编译词法分析器和解析器。

build.rs文件

use cfgrammar::yacc::YaccKind;
use lrlex::CTLexerBuilder;

fn main() {
    CTLexerBuilder::new()
        .lrpar_config(|ctp| {
            ctp.yacckind(YaccKind::Grmtools)
                .grammar_in_src_dir("calc.y")
                .unwrap()
        })
        .lexer_in_src_dir("calc.l")
        .unwrap()
        .build()
        .unwrap();
}

calc.l词法分析器文件

%%
[0-9]+ "INT"
\+ "+"
\* "*"
\( "("
\) ")"
[\t ]+ ;

calc.y语法文件

%start Expr
%avoid_insert "INT"
%%
Expr -> Result<u64, ()>:
      Expr '+' Term { Ok($1? + $3?) }
    | Term { $1 }
    ;

Term -> Result<u64, ()>:
      Term '*' Factor { Ok($1? * $3?) }
    | Factor { $1 }
    ;

Factor -> Result<u64, ()>:
      '(' Expr ')' { $2 }
    | 'INT'
      {
          let v = $1.map_err(|_| ())?;
          parse_int($lexer.span_str(v.span()))
      }
    ;
%%
// 这里定义的函数对所有语法动作都可见

fn parse_int(s: &str) -> Result<u64, ()> {
    match s.parse::<u64>() {
        Ok(val) => Ok(val),
        Err(_) => {
            eprintln!("{} cannot be represented as a u64", s);
            Err(())
        }
    }
}

src/main.rs主文件

use std::io::{self, BufRead, Write};

use lrlex::lrlex_mod;
use lrpar::lrpar_mod;

// 使用`lrlex_mod!`将`calc.l`的词法分析器引入作用域
lrlex_mod!("calc.l");
// 使用`lrpar_mod!`将`calc.y`的解析器引入作用域
lrpar_mod!("calc.y");

fn main() {
    // 获取`calc`语言的`LexerDef`
    let lexerdef = calc_l::lexerdef();
    let stdin = io::stdin();
    loop {
        print!(">>> ");
        io::stdout().flush().ok();
        match stdin.lock().lines().next() {
            Some(Ok(ref l)) => {
                if l.trim().is_empty() {
                    continue;
                }
                // 创建一个词法分析器来对输入进行词法分析
                let lexer = lexerdef.lexer(l);
                // 将词法分析器传递给解析器,对输入进行词法分析和解析
                let (res, errs) = calc_y::parse(&lexer);
                for e in errs {
                    println!("{}", e.pp(&lexer, &calc_y::token_epp));
                }
                match res {
                    Some(Ok(r)) => println!("Result: {}", r),
                    _ => eprintln!("Unable to evaluate expression.")
                }
            }
            _ => break
        }
    }
}

完整示例运行

我们可以使用cargo run运行项目并评估简单表达式:

>>> 2 + 3
Result: 5
>>> 2 + 3 * 4
Result: 14
>>> (2 + 3) * 4
Result: 20

lrpar还内置了高级错误恢复功能:

>>> 2 + + 3
Parsing error at line 1 column 5. Repair sequences found:
   1: Delete +
   2: Insert INT
Result: 5
>>> 2 + 3 3
Parsing error at line 1 column 7. Repair sequences found:
   1: Insert *
   2: Insert +
   3: Delete 3
Result: 11
>>> 2 + 3 4 5
Parsing error at line 1 column 7. Repair sequences found:
   1: Insert *, Delete 4
   2: Insert +, Delete 4
   3: Delete 4, Delete 5
   4: Insert +, Shift 4, Delete 5
   5: Insert +, Shift 4, Insert +
   6: Insert *, Shift 4, Delete 5
   7: Insert *, Shift 4, Insert *
   8: Insert *, Shift 4, Insert +
   9: Insert +, Shift 4, Insert *
Result: 17

1 回复

Rust语法解析与词法分析库lrpar的使用指南

完整示例

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

1. 项目结构

calculator/
├── Cargo.toml
├── build.rs
├── src/
│   ├── main.rs
│   ├── calc.lex
│   └── calc.y

2. Cargo.toml

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

[dependencies]
lrpar = "0.11"
lrlex = "0.11"
cfgrammar = "0.11"

3. build.rs

use cfgrammar::yacc::YaccKind;
use lrlex::LexerBuilder;
use lrpar::CTParserBuilder;

fn main() -> Result<(), Box<dyn std::error::Error>> {
    let lex_rule = LexerBuilder::new()
        .rule_files(vec!["src/calc.lex"])
        .build()?;
    
    CTParserBuilder::new()
        .yacckind(YaccKind::Grmtools)
        .grammar_files(vec!["src/calc.y"])
        .lexer(lex_rule)
        .build()?;
    
    Ok(())
}

4. src/calc.lex

%%
[0-9]+ "INT"
\+ "+"
\* "*"
\( "("
\) ")"
[\t\n ]+ ;

5. src/calc.y

%start Expr
%%
Expr -> Result<u64, ()>:
      Expr '+' Term { Ok($1? + $3?) }
    | Term { $1 }
    ;

Term -> Result<u64, ()>:
      Term '*' Factor { Ok($1? * $3?) }
    | Factor { $1 }
    ;

Factor -> Result<u64, ()>:
      '(' Expr ')' { $2 }
    | 'INT' {
        let v = $1.map_err(|_| ())?;
        parse_int($lexer.span_str(v.span()))
      }
    ;
%%
// 辅助函数:将字符串转换为整数
fn parse_int(s: &str) -> Result<u64, ()> {
    match s.parse::<u64>() {
        Ok(val) => Ok(val),
        Err(_) => Err(())
    }
}

6. src/main.rs

use lrlex::lrlex_mod;
use lrpar::lrpar_mod;
use std::io::{self, Write};

// 生成词法分析和语法分析模块
lrlex_mod!("calc.lex");
lrpar_mod!("calc.y");

fn main() {
    println!("简单计算器 (输入q退出)");
    
    loop {
        print!(">>> ");
        io::stdout().flush().unwrap();
        
        let mut input = String::new();
        io::stdin().read_line(&mut input).unwrap();
        
        if input.trim() == "q" {
            break;
        }
        
        let lexerdef = calc_lex::lexerdef();
        let lexer = lexerdef.lexer(&input);
        let (res, errs) = calc_y::parse(&lexer);
        
        // 输出错误信息
        for e in errs {
            println!("错误: {:?}", e);
        }
        
        // 输出结果
        match res {
            Some(Ok(r)) => println!("结果: {}", r),
            _ => println!("无法计算表达式")
        }
    }
}

7. 运行示例

编译并运行项目:

cargo run

示例交互:

简单计算器 (输入q退出)
>>> 3 + 5 * 2
结果: 13
>>> (4 + 5) * 2
结果: 18
>>> q

示例说明

  1. 这个完整示例实现了一个支持加减乘除和括号的计算器
  2. 词法分析器(lex)识别数字和运算符
  3. 语法分析器(y)定义了运算优先级和结合性
  4. 主程序提供了交互式REPL环境
  5. 包含错误处理功能,能报告解析错误

这个示例展示了lrpar和lrlex的基本用法,你可以根据需要扩展语法规则来实现更复杂的功能。

回到顶部