Rust Erlang词法分析库erl_tokenize的使用,实现高效的Erlang源代码分词与语法解析
Rust Erlang词法分析库erl_tokenize的使用,实现高效的Erlang源代码分词与语法解析
简介
erl_tokenize
是一个用Rust编写的Erlang源代码分词器库。
示例
基础分词示例
将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));
}
性能提示
- 对于大型文件,考虑使用
Lexer::with_offset
来处理文件片段 - 重用
Lexer
实例可以减少分配 - 如果不需要位置信息,可以禁用位置跟踪以提高性能
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);
}
这个完整示例展示了:
- 使用
LexerBuilder
配置词法分析器 - 遍历并打印所有token及其位置信息
- 统计Erlang模块中的函数定义数量
- 查找所有的远程函数调用
erl_tokenize
库为Rust开发者提供了强大的Erlang源代码处理能力,可以用于构建代码分析工具、语法高亮、格式化工具等应用。