5 个版本
使用旧的 Rust 2015
0.1.4 | 2020年11月8日 |
---|---|
0.1.3 | 2020年10月22日 |
0.1.2 | 2018年3月15日 |
0.1.1 | 2018年3月15日 |
0.1.0 | 2016年12月14日 |
在 文本处理 中排名第 328
每月下载量 3,168
用于 35 个 crate(其中 2 个直接使用)
34KB
689 行
scanlex - 一个简单的词法扫描器。
输入问题
写入东西比读取它们更容易,因为可能出错的地方更多。读取可能会失败,文本可能不是有效的 UTF-8,数字可能格式不正确或超出范围。
词法扫描器
词法扫描器将字符流分割成 标记。通过反复调用 Scanner
的 get
方法(如果没有标记剩下,将返回 Token::End
)或通过迭代扫描器来返回标记。它们代表数字、字符、标识符或单引号/双引号字符串。还有一个 Token::Error
来指示格式错误的标记。
此词法扫描器做了一些假设,例如数字不能直接跟在字母后面等。此版本没有尝试解码字符串中的 C 风格转义字符。所有空白都被忽略。它旨在处理通用结构化数据,而不是代码。
例如,字符串 "hello 'dolly' * 42" 将被分割成四个标记
- 一个 标识符 'hello'
- 一个引号字符串 'dolly'
- 一个字符 '*'
- 和一个数字 42
extern crate scanlex;
use scanlex::{Scanner,Token};
let mut scan = Scanner::new("hello 'dolly' * 42");
assert_eq!(scan.get(),Token::Iden("hello".into()));
assert_eq!(scan.get(),Token::Str("dolly".into()));
assert_eq!(scan.get(),Token::Char('*'));
assert_eq!(scan.get(),Token::Int(10));
assert_eq!(scan.get(),Token::End);
要提取值,可以使用如下代码
let greeting = scan.get_iden()?;
let person = scan.get_string()?;
let op = scan.get_char()?;
let answer = scan.get_integer(); // i64
Scanner
实现 Iterator
。如果您只想从字符串中提取单词,那么使用 as_iden
过滤器将可以完成这项任务,因为它返回 Option<String>
let s = Scanner::new("bonzo 42 dog (cat)");
let v: Vec<_> = s.filter_map(|t| t.as_iden()).collect();
assert_eq!(v,&["bonzo","dog","cat"]);
使用 as_number
,您可以采用此策略从文档中提取所有数字,忽略所有其他结构。在 scan.rs
示例中,您可以看到通过命令行解析给定字符串时生成的标记。
此迭代器仅在 Token::End
处停止 - 您可以自己处理 Token::Error
。
通常,不忽略结构是很重要的。比如说,我们有一些输入字符串,看起来像这样 "(WORD) = NUMBER"
scan.skip_chars("(")?;
let word = scan.get_iden()?;
scan.skip_chars(")=")?;
let num = scan.get_number()?;
任何这些调用都可能失败!
为从可读源读取的每一行文本创建一个扫描器是一种常见的模式。《scanline.rs》示例展示了如何使用《ScanLines》来实现这一点。
let f = File::open("scanline.rs").expect("cannot open scanline.rs");
let mut iter = ScanLines::new(&f);
while let Some(s) = iter.next() {
let mut s = s.expect("cannot read line");
// show the first token of each line
println!("{:?}",s.get());
}
一个更严重的例子(来自测试)是解析JSON
type JsonArray = Vec<Box<Value>>;
type JsonObject = HashMap<String,Box<Value>>;
#[derive(Debug, Clone, PartialEq)]
pub enum Value {
Str(String),
Num(f64),
Bool(bool),
Arr(JsonArray),
Obj(JsonObject),
Null
}
fn scan_json(scan: &mut Scanner) -> Result<Value,ScanError> {
use Value::*;
match scan.get() {
Token::Str(s) => Ok(Str(s)),
Token::Num(x) => Ok(Num(x)),
Token::Int(n) => Ok(Num(n as f64)),
Token::End => Err(scan.scan_error("unexpected end of input",None)),
Token::Error(e) => Err(e),
Token::Iden(s) =>
if s == "null" {Ok(Null)}
else if s == "true" {Ok(Bool(true))}
else if s == "false" {Ok(Bool(false))}
else {Err(scan.scan_error(&format!("unknown identifier '{}'",s),None))},
Token::Char(c) =>
if c == '[' {
let mut ja = Vec::new();
let mut ch = c;
while ch != ']' {
let o = scan_json(scan)?;
ch = scan.get_ch_matching(&[',',']'])?;
ja.push(Box::new(o));
}
Ok(Arr(ja))
} else
if c == '{' {
let mut jo = HashMap::new();
let mut ch = c;
while ch != '}' {
let key = scan.get_string()?;
scan.get_ch_matching(&[':'])?;
let o = scan_json(scan)?;
ch = scan.get_ch_matching(&[',','}'])?;
jo.insert(key,Box::new(o));
}
Ok(Obj(jo))
} else {
Err(scan.scan_error(&format!("bad char '{}'",c),None))
}
}
}
(这当然是一个示例。JSON是一个已解决的问题。)
选项
使用《no_float》时,你得到一个基本的解析器,它不识别浮点数,只识别整数、字符串、字符和标识符。如果现有规则太严格,这很有用——例如,在《no_float》模式下,“2d”是可以接受的,但在默认模式下会出错。《chrono-english》使用此模式来解析日期表达式。chrono-english
使用《line_comment》时,你提供一个字符;在此字符之后,当前行的其余部分将被忽略。