Rust Erlang词法分析库erl_tokenize的使用,实现高效的Erlang源代码分词与语法解析

Rust Erlang词法分析库erl_tokenize的使用,实现高效的Erlang源代码分词与语法解析

简介

erl_tokenize是一个用Rust编写的Erlang源代码分词器库。

erl_tokenize Documentation Actions Status License

示例

基础分词示例

将Erlang代码io:format("Hello").进行分词:

use erl_tokenize::Tokenizer;

let src = r#"io:format("Hello")."#;
let tokenizer = Tokenizer::new(src);
let tokens = tokenizer.collect::<Result<Vec<_>, _>>().unwrap();

assert_eq!(tokens.iter().map(|t| t.text()).collect::<Vec<_>>(),
           ["io", ":", "format", "(", r#""Hello""#, ")", "."]);

完整示例代码

下面是一个更完整的示例,展示了如何分析Erlang模块文件:

use erl_tokenize::Tokenizer;
use std::fs;

fn main() -> Result<(), Box<dyn std::error::Error>> {
    // 读取Erlang源文件
    let src = fs::read_to_string("example.erl")?;
    
    // 创建分词器实例
    let tokenizer = Tokenizer::new(&src);
    
    // 收集所有token并处理错误
    let tokens = tokenizer.collect::<Result<Vec<_>, _>>()?;
    
    // 打印每个token的文本和类型
    for token in tokens {
        println!("{:?} => {}", token.value(), token.text());
    }
    
    Ok(())
}

命令行示例

执行tokenize示例命令:

$ cargo run --example tokenize -- /dev/stdin <<EOS
-module(foo).

-export([bar/0]).

bar() -> qux.
EOS

[Position { filepath: None, offset: 0, line: 1, column: 1 }] Symbol(Hyphen)
[Position { filepath: None, offset: 1, line: 1, column: 2 }] Atom("module")
[Position { filepath: None, offset: 7, line: 1, column: 8 }] Symbol(OpenParen)
[Position { filepath: None, offset: 8, line: 1, column: 9 }] Atom("foo")
[Position { filepath: None, offset: 11, line: 1, column: 12 }] Symbol(CloseParen)
[Position极佳,以下是一个更完整的示例,展示如何处理更复杂的Erlang代码并提取位置信息:

```rust
use erl_tokenize::{Tokenizer, Position};
use std::path::PathBuf;

fn tokenize_with_position(src: &str, filepath: Option<PathBuf>) {
    let mut tokenizer = Tokenizer::new(src);
    if let Some(path) = filepath {
        tokenizer.set_filepath(path);
    }

    for token in tokenizer {
        match token {
            Ok(t) => {
                let pos = t.position();
                println!("[Line {}, Column {}] {:?} => {}",
                         pos.line(),
                         pos.column(),
                         t.value(),
                         t.text());
            },
            Err(e) => eprintln!("Error: {}", e),
        }
    }
}

fn main() {
    let code = r#"
-module(complex).
-export([calculate/2]).

calculate(A, B) when is_integer(A), is_integer(B) ->
    A + B;
calculate(_, _) ->
    error(badarg).
"#;
    tokenize_with_position(code, Some("complex.erl".into()));
}

安装

在项目目录中运行以下Cargo命令:

cargo add erl_tokenize

或者在Cargo.toml中添加以下行:

erl_tokenize = "0.8.3"

参考

  • erl_scan模块
  • Erlang数据类型

1 回复

Rust Erlang词法分析库erl_tokenize的使用指南

介绍

erl_tokenize是一个用Rust编写的Erlang源代码词法分析库,它能够高效地将Erlang源代码分解为一系列标记(tokens),为后续的语法分析和编译提供基础。

该库的主要特点包括:

  • 完整的Erlang词法支持
  • 高性能的解析能力
  • 准确的源代码位置跟踪
  • 良好的错误处理机制

安装

在Cargo.toml中添加依赖:

[dependencies]
erl_tokenize = "0.3"

基本使用方法

1. 简单的词法分析

use erl_tokenize::{Lexer, Token};

fn main() {
    let src = r#"io:format("Hello, ~s!~n", ["World"])"#;
    let mut lexer = Lexer::new(src);
    
    while let Some(token) = lexer.next().unwrap() {
        println!("{:?}", token);
    }
}

2. 处理模块定义

use erl_tokenize::{Lexer, Token};

fn tokenize_module(module_src: &str) {
    let mut lexer = Lexer::new(module_src);
    while let Some(token) = lexer.next().unwrap() {
        match token.value() {
            erl_tokenize::values::Atom(atom) if atom == "module" => {
                println!("Found module declaration");
            }
            _ => {}
        }
    }
}

fn main() {
    let src = r#"
        -module(hello).
        -export([greet/0]).
        
        greet() -> io:format("Hello world!~n").
    "#;
    tokenize_module(src);
}

3. 获取标记位置信息

use erl_tokenize::{Lexer, Position};

fn print_token_positions(src: &str) {
    let mut lexer = Lexer::new(src);
    while let Some(token) = lexer.next().unwrap() {
        let start = token.start_position();
        let end = token.end_position();
        println!(
            "Token '{}' at {}:{} to {}:{}",
            token.text(),
            start.line(),
            start.column(),
            end.line(),
            end.column()
        );
    }
}

fn main() {
    let src = r#"
        add(X, Y) ->
            X + Y.
    "#;
    print_token_positions(src);
}

高级用法

1. 自定义错误处理

use erl_tokenize::{Lexer, Error};

fn tokenize_with_error_handling(src: &str) {
    let mut lexer = Lexer:: new(src);
    loop {
        match lexer.next() {
            Ok(Some(token)) => println!("Token: {:?}", token),
            Ok(None) => break, // 输入结束
            Err(e) => {
                eprintln!("Lexical error at {}:{} - {}",
                    e.position().line(),
                    e.position().column(),
                    e.kind()
                );
                // 尝试从错误中恢复
                lexer.recover();
            }
        }
    }
}

fn main() {
    let src = r#"
        test() ->
            1 + $%  % 非法字符
    "#;
    tokenize_with_error_handling(src);
}

2. 过滤特定标记

use erl_tokenize::{Lexer, Token};

fn find_function_calls(src: &str) {
    let mut lexer = Lexer::new(src);
    let mut tokens: Vec<Token> = lexer.collect::<Result<_, _>>().unwrap();
    
    // 查找函数调用模式: Atom:Atom( 或 Atom(
    for window in tokens.windows(3) {
        if let (Some(module), Some(colon), Some(func)) = (&window[0], &window[1], &window[2]) {
            if colon.text() == ":" {
                if let (Some(module_name), Some(func_name)) = 
                    (module.as_atom(), func.as_atom()) 
                {
                    println!("Found remote call: {}:{}", module_name, func_name);
                }
            }
        }
        
        if let Some(func) = &window[0] {
            if let Some(func_name) = func.as_atom() {
                if let Some(next) = &window[1] {
                    if next.text() == "(" {
                        println!("Found local call: {}", func_name);
                    }
                }
            }
        }
    }
}

fn main() {
    let src = r#"
        main() ->
            io:format("Hello"),
            local_func().
    "#;
    find_function_calls(src);
}

实际应用示例

统计Erlang模块中的函数定义

use erl_tokenize::{Lexer, Token};

fn count_functions(src: &str) -> usize {
    let mut lexer = Lexer::new(src);
    let mut count = 0;
    let mut tokens: Vec<Token> = lexer.collect::<Result<_, _>>().unwrap();
    
    // 查找模式: Atom( -> ... ) ->
    for window in tokens.windows(4) {
        if let (Some(name), Some(lparen), _, Some(rarrow)) = 
            (&window[0], &window[1], &window[2], &window[3]) 
        {
            if name.as_atom().is_some() && 
               lparen.text() == "(" && 
               rarrow.text() == "->" 
            {
                count += 1;
            }
        }
    }
    
    count
}

fn main() {
    let src = r#"
        -module(sample).
        
        func1() -> ok.
        func2(X) -> {ok, X}.
        func3(X, Y) when is_integer(X), is_integer(Y) -> X + Y.
    "#;
    
    println!("Found {} functions", count_functions(src));
}

性能提示

  1. 对于大型文件,考虑使用Lexer::with_offset来处理文件片段
  2. 重用Lexer实例可以减少分配
  3. 如果不需要位置信息,可以禁用位置跟踪以提高性能
use erl_tokenize::{Lexer, LexerBuilder};

fn fast_tokenize(src: &str) {
    let lexer = LexerBuilder::new()
        .track_positions(false)  // 禁用位置跟踪
        .build(src);
    
    // 处理标记...
}

完整示例

下面是一个整合了多个功能的完整示例,展示如何使用erl_tokenize进行Erlang代码分析:

use erl_tokenize::{Lexer, Token, Position, Error, LexerBuilder};
use erl_tokenize::values::Atom;

fn analyze_erlang_code(src: &str) {
    println!("===== Erlang代码分析报告 =====");
    
    // 配置Lexer
    let lexer = LexerBuilder::new()
        .track_positions(true)
        .build(src);
    
    // 1. 显示所有token及其位置
    println!("\n--- 所有Token及其位置 ---");
    let tokens: Vec<Token> = lexer.collect::<Result<_, _>>().unwrap();
    for token in &tokens {
        let pos = token.start_position();
        println!("[{},{}] {}: {:?}", 
            pos.line(), 
            pos.column(),
            token.text(),
            token.value()
        );
    }
    
    // 2. 统计函数数量
    println!("\n--- 函数统计 ---");
    let function_count = tokens.windows(4)
        .filter(|w| {
            matches!(w, 
                [name, lparen, _, rarrow] 
                if name.as_atom().is_some() 
                && lparen.text() == "(" 
                && rarrow.text() == "->"
            )
        })
        .count();
    println!("找到 {} 个函数定义", function_count);
    
    // 3. 查找远程调用
    println!("\n--- 远程函数调用 ---");
    for window in tokens.windows(3) {
        if let [module, colon, func] = window {
            if colon.text() == ":" {
                if let (Some(module_name), Some(func_name)) = 
                    (module.as_atom(), func.as_atom()) 
                {
                    println!("远程调用: {}:{}", module_name, func_name);
                }
            }
        }
    }
}

fn main() {
    let src = r#"
        -module(sample).
        -export([start/0, add/2]).
        
        start() ->
            io:format("Starting...~n"),
            Result = add(1, 2),
            io:format("Result: ~p~n", [Result]).
        
        add(X, Y) when is_integer(X), is_integer(Y) ->
            X + Y;
        add(_, _) ->
            {error, badarg}.
    "#;
    
    analyze_erlang_code(src);
}

这个完整示例展示了:

  1. 使用LexerBuilder配置词法分析器
  2. 遍历并打印所有token及其位置信息
  3. 统计Erlang模块中的函数定义数量
  4. 查找所有的远程函数调用

erl_tokenize库为Rust开发者提供了强大的Erlang源代码处理能力,可以用于构建代码分析工具、语法高亮、格式化工具等应用。

回到顶部