「Rust 重写 sqlite」REPL
REPL
既然我们是以SQLite为模型,那么我们不妨试着照搬SQLite的样子,以方便用户使用。当你运行sqlite3
时,SQLite启动一个 读-执行-打印 的循环,又称:REPL
。
❯ sqlite3
SQLite version 3.32.3 2020-06-18 14:16:19
Enter ".help" for usage hints.
Connected to a transient in-memory database.
Use ".open FILENAME" to reopen on a persistent database.
sqlite> .databases
main:
sqlite> .dadada
Error: unknown command or invalid arguments: "dadada". Enter ".help" for help
sqlite> .exit
复制代码
首先为了实现上述结果,这里要做出一些设计选择。由于这个项目的重点是研究和建立一个数据库,大部分开发精力集中于此,这意味着我不想花大部分时间重新发明轮子和编写CLI解释器或REPL逻辑。因此,对于这些,我决定利用已经开发的、有点成熟的第三方库来完成。不过也许在未来,如果有一些空闲时间,并且使用这些第三方库确实影响了应用程序的整体性能,我可以随时回来替换。
REPL的逻辑是非常直接:
一个无限循环中,打印一个提示,获得一个输入行,验证然后处理该行。
我决定使用 rustyline crate
,它已经相当成熟,内存效率高,而且已经解决了很多我们必须处理的问题,甚至从用户体验方面来说,例如,实时提供提示和自动完成,这是一个非常好的功能。
因此,在正式编写代码前,你可以在Github上找到它。下面我将使用一个demo snippet,快速演示 rustyline
是如何通过一个简单的例子工作的:
首先,需要在你的 cargo.toml
中添加该依赖关系:
[dependencies]
rustyline = "7.1.0"
复制代码
main.rs
中:
use rustyline::error::ReadlineError;
use rustyline::Editor;
fn main() {
// 创建一个具有默认配置选项的编辑器
let mut repl = Editor::<()>::new();
// 加载了一个带有历史命令的文件。如果该文件不存在,它会创建一个
if repl.load_history(".repl_history").is_err() {
println!("No previous history.");
}
// 无限循环,一直卡在这里,直到用户终止程序
loop {
// 要求用户输入一个命令。你可以在这里添加任何你想要的东西作为前缀
let readline = repl.readline(">> ");
// readline返回一个结果。然后用匹配语句来过滤这个结果
match readline {
Ok(line) => {
repl.add_history_entry(line.as_str());
println!("Line: {}", line);
},
Err(ReadlineError::Interrupted) => {
println!("CTRL-C");
break
},
Err(ReadlineError::Eof) => {
println!("CTRL-D");
break
},
Err(err) => {
println!("Error: {:?}", err);
break
}
}
}
// 把命令保存到文件中。到现在为止,它们都储存在内存中
repl.save_history(".repl_history").unwrap();
}
复制代码
就这样,有了上述代码,你就有了一个基本的REPL程序,开始开始运行。
相关文章