3个版本
使用旧的Rust 2015
0.1.2 | 2017年6月22日 |
---|---|
0.1.1 | 2017年4月11日 |
0.1.0 | 2017年2月15日 |
#9 in #parse-error
100KB
2.5K SLoC
Snoot
"解析s表达式很简单"你说。嗯,是的 - 但是 - 你的假设s表达式解析器
- 解析时在源文本上不进行复制吗?
- 带有错误报告和格式化机制吗?
- 遇到第一个解析错误后继续解析吗?
- 有一个像“Snoot”这样可爱的名字吗?
至少,对最后一个问题的答案是明确的“不”。
零拷贝解析
因为解析函数接受Tendril
而不是String
,你不必担心跟踪你的解析值的生命周期。
这是魔法吗?也许吧!
错误报告
你知道Rust编译器会输出美丽且信息丰富的错误消息吗?使用Snoot,你可以轻松地制作出这样的错误消息!使用从解析中获得的Span对象之一,你可以在不触及任何脏活的情况下构建包含嵌入源的错误消息。
解析弹性
你的自定义解析器在遇到第一个解析错误后是不是会哭鼻子?悲伤。Snoot面对困难时依然勇往直前,以便你能向用户报告尽可能多的错误。
示例
错误格式化
extern crate snoot;
use snoot::simple_parse;
use snoot::error::{ErrorBuilder, ErrorLevel};
const PROGRAM: &'static str = "
(define map (lambda (xs f)
(if (nil xs) xs
(cons (f (car xs))
(map (cdr xs) f)))))
";
fn main() {
let snoot::ParseResult{roots, diagnostics} = simple_parse(PROGRAM);
assert!(diagnostics.is_empty());
// Report an error over the entire program
let span = roots[0].span();
let error = ErrorBuilder::new("this is the message", span.clone())
.with_file_name("filename.lisp")
.with_error_level(ErrorLevel::Error)
.build();
println!("{}", error);
}
输出
error: this is the message
--> filename.lisp:2:1
2 | (define map (lambda (xs f)
3 | (if (nil xs) xs
4 | (cons (f (car xs))
5 | (map (cdr xs) f)))))
解析
extern crate snoot;
const PROGRAM: &'static str = "
(hello world
(片仮名
(العَرَبِيَّة)))
";
fn main() {
let snoot::ParseResult{roots, diagnostics} = snoot::simple_parse(PROGRAM);
assert!(diagnostics.is_empty());
println!("{:#?}", roots);
}
输出
[
List {
opening_token: TokenInfo { line_number: 2, column_number: 1, byte_offset: 1, typ: ListOpening(0), string: "(" },
closing_token: TokenInfo { line_number: 4, column_number: 26, byte_offset: 70, typ: ListClosing(0), string: ")" },
span: Span {
text: "(hello world\n (片仮名\n (العَرَبِيَّة\u{200e}\u{200e})))",
lines: "\n(hello world\n (片仮名\n (العَرَبِيَّة\u{200e}\u{200e})))",
line_start: 2, column_start: 1, byte_start: 1, line_end: 4, column_end: 27, byte_end: 71
},
children: [
Terminal(
TokenInfo { line_number: 2, column_number: 2, byte_offset: 2, typ: Atom, string: "hello" },
Span {
text: "hello",
lines: "\n(hello world",
line_start: 2, column_start: 2, byte_start: 2, line_end: 2, column_end: 7, byte_end: 7
}
),
Terminal(
TokenInfo { line_number: 2, column_number: 8, byte_offset: 8, typ: Atom, string: "world" },
Span {
text: "world",
lines: "\n(hello world",
line_start: 2, column_start: 8, byte_start: 8, line_end: 2, column_end: 13, byte_end: 13
}
),
List {
opening_token: TokenInfo { line_number: 3, column_number: 5, byte_offset: 18, typ: ListOpening(0), string: "(" },
closing_token: TokenInfo { line_number: 4, column_number: 25, byte_offset: 69, typ: ListClosing(0), string: ")" },
span: Span {
text: "(片仮名\n (العَرَبِيَّة\u{200e}\u{200e}))",
lines: " (片仮名\n (العَرَبِيَّة\u{200e}\u{200e})))",
line_start: 3, column_start: 5, byte_start: 18, line_end: 4, column_end: 26, byte_end: 70
},
children: [
Terminal(
TokenInfo { line_number: 3, column_number: 6, byte_offset: 19, typ: Atom, string: "片仮名" },
Span {
text: "片仮名",
lines: " (片仮名",
line_start: 3, column_start: 6, byte_start: 19, line_end: 3, column_end: 9, byte_end: 28
}
),
List {
opening_token: TokenInfo { line_number: 4, column_number: 9, byte_offset: 37, typ: ListOpening(0), string: "(" },
closing_token: TokenInfo { line_number: 4, column_number: 24, byte_offset: 68, typ: ListClosing(0), string: ")" },
span: Span {
text: "(العَرَبِيَّة\u{200e}\u{200e})",
lines: " (العَرَبِيَّة\u{200e}\u{200e})))",
line_start: 4, column_start: 9, byte_start: 37, line_end: 4, column_end: 25, byte_end: 69
},
children: [
Terminal(
TokenInfo { line_number: 4, column_number: 10, byte_offset: 38, typ: Atom, string: "العَرَبِيَّة\u{200e}\u{200e}"
},
Span {
text: "العَرَبِيَّة\u{200e}\u{200e}",
lines: " (العَرَبِيَّة\u{200e}\u{200e})))",
line_start: 4, column_start: 10, byte_start: 38, line_end: 4, column_end: 24, byte_end: 68
}
)
]
}
]
}
]
}
]
你知道吗,这个readme中的提交比整个项目的其余部分还要多!
依赖项
~8MB
~156K SLoC