#input #tokenize #scan #text #text-parser

scanlex

一个简单的文本解析器,用于将文本解析成标记

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

Download history 3042/week @ 2024-03-13 4393/week @ 2024-03-20 3483/week @ 2024-03-27 1548/week @ 2024-04-03 1174/week @ 2024-04-10 981/week @ 2024-04-17 1034/week @ 2024-04-24 1005/week @ 2024-05-01 1034/week @ 2024-05-08 710/week @ 2024-05-15 898/week @ 2024-05-22 959/week @ 2024-05-29 815/week @ 2024-06-05 663/week @ 2024-06-12 729/week @ 2024-06-19 832/week @ 2024-06-26

每月下载量 3,168
用于 35 crate(其中 2 个直接使用)

MIT 许可协议

34KB
689

scanlex - 一个简单的词法扫描器。

输入问题

写入东西比读取它们更容易,因为可能出错的地方更多。读取可能会失败,文本可能不是有效的 UTF-8,数字可能格式不正确或超出范围。

词法扫描器

词法扫描器将字符流分割成 标记。通过反复调用 Scannerget 方法(如果没有标记剩下,将返回 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》时,你提供一个字符;在此字符之后,当前行的其余部分将被忽略。

无运行时依赖