8 个版本 (3 个稳定)
1.0.2 | 2024 年 4 月 10 日 |
---|---|
1.0.1 | 2024 年 2 月 3 日 |
1.0.0 | 2024 年 1 月 27 日 |
0.2.0 | 2024 年 1 月 25 日 |
0.1.3 | 2024 年 1 月 24 日 |
#365 在 解析器实现 中
在 2 个crate中使用(通过 simpleml)
2MB
1.5K SLoC
WhitespaceSV
概述
白空间分隔值 (WSV) 是一种文件格式,旨在解决逗号分隔值 (CSV) 文件格式的问题。它可以无歧义地解析,并且不需要任何配置即可由解析器解析。
此 crate 提供了基于 Rust 的 WSV 标准 实现。此实现旨在尽可能高效。在急切(非懒或标准)解析中,此解析器尽可能接近零拷贝。它仅在需要替换转义序列的情况下分配字符串。crate 中仅公开了少量 API,但它们应该能够处理所有用例。
更新说明
1.0.2
修复了 写入交错数组时的恐慌
解析
要使用此 crate 解析 WSV 文件,只需调用提供的解析函数之一。目前有 3 个,所以请选择适合您用例的一个。大多数用例可能应该使用标准 parse() 函数。
- parse_with_col_count - 如果可以安全地急切解析您的 WSV(它适合内存)并且您的 WSV 是具有已知列数的标准表格,请使用此 API。这将避免解析过程中不必要的 Vecs 重新分配。
- parse_lazy - 如果您有一个大输入,应该只分批加载(可能是因为它不适合内存),请使用此 API。此 API 将逐行懒解析输入。如果您需要逐值解析,请直接使用 WSVLazyTokenizer 以获得完全控制。
- parse - 对于所有其他情况请使用此函数。
急切解析
这里没有太多要说的。急切解析 API 的工作方式与您预期的差不多。它们返回一个 Result,如果解析成功,则值为 Ok 变体,其中包含 Option<Cow<'_, str>> 的二维 Vec。Cow 中的值已处理以下情况
"/"
转义序列已被替换为\n
""
转义序列已被替换为"
- 任何包裹的引号都将被移除。例如:
"hello, world!"
将变为hello, world
-
值将作为 Option 枚举的 None 变体返回。其他所有内容都将是一个 Some 变体。
例如,下面的输入将返回 [[Some(1), None], [Some(3), Some(String(This is a string " with escape sequences \n))]] (为了清晰起见,已删除字符串值周围的引号和 \
转义序列)
1 -
3 "This is a string "" with escape sequences "/""
惰性解析
除了标准解析之外,此 crate 还支持通过迭代器进行惰性解析。通过创建一个迭代器管道,您可以处理不适合内存的文件。例如,假设我有一个 300 GB 的文件,我真正想要的是该文件每行的总和。我可以设置一个迭代器管道来读取 WSV 并将总和输出回 WSV,如下面的代码所示。
请注意,示例代码仍然会贪婪地评估 WSV 的每一行。如果您需要进行更细粒度的惰性解析,请直接使用此 crate 的 WSVLazyTokenizer 来完成您所需的操作。
惰性解析 API 和 WSVLazyTokenizer 接受一个 char
迭代器,以下是一些有用的资源来获取此迭代器
- utf8-chars crate
- 标准库中的 from_utf16 (nightly)
- 标准库中的 from_utf16le (nightly)
- 标准库中的 from_utf16be (nightly)
- widestring crate 中的 decode_utf32 用于 utf-32
use std::fs::File;
use std::io::BufReader;
// I recommend you pull in the utf8-chars crate as a
// dependency if you need lazy parsing of utf-8
use whitespacesv::{parse_lazy, WSVWriter};
use utf8_chars::BufReadCharsExt;
let mut reader =
BufReader::new(
File::open("./my_very_large_file.txt")
.unwrap());
let chars = reader.chars().map(|ch| ch.unwrap());
let lines_lazy = parse_lazy(chars).map(|line| {
// For this example we will assume we have valid WSV
let sum = line
.unwrap()
.into_iter()
// We're counting None as 0 in my case,
// so flat_map the Nones out.
.flat_map(|opt| opt)
.map(|value| value.parse::<i32>().unwrap_or(0))
.sum::<i32>();
// The writer needs a 2D iterator of Option<String>,
// so wrap the value in a Some and .to_string() it.
// Also wrap in a Vec to make it a 2D iterator
vec![Some(sum.to_string())]
});
// CAREFUL: Don't call .collect() here or we'll run out of memory!
// The WSVWriter when using ColumnAlignment::Packed
// (the default) is also lazy, so we can pass our
// result in directly.
for ch in WSVWriter::new(lines_lazy) {
// Your code to dump the output to a file goes here.
print!("{}", ch);
}
写入
有两种方式可以使用提供的 API 来写入 WSV 文件。
- WSVWriter 的 to_string() 方法 - 这允许您根据需要将列对齐到左侧或右侧。大多数用例应该使用此方法。
- WSVWriter 迭代器实现。这允许您懒惰地评估值。如果您需要写入过大而无法放入内存中的值存储,请使用此方法。此实现不尊重列对齐,并且是为纯速度而构建的。
to_string()
此 API 将仅在必要时用引号包围字符串。此 2D IntoIterator 结构中的值必须是 Options,其中内部值是实现了
- AsRef,
- From<&'static str>, 和
- ToString.
&str, Cow<'_, str>, String, 和 &String 类型都支持这些类型约束。
通过 WSVWriter::new() API 支持的一些类型示例
LinkedList<LinkedList<Option<Cow<'_, str>>>>
Vec<Vec<Option<&'_ str>>>
VecDeque<Vec<Option<&String>>>
Iter<Iter<Option<String>>>
其中 Iter 是任何实现了 Iterator 的类型。
use whitespacesv::{WSVWriter, ColumnAlignment};
let values = vec![
vec!["1", "2", "3"], // In this example, each value is &str,
vec!["4", "5", "6"], // but String and Cow<'_, str> also work
vec!["My string with a \n character"],
vec!["My string with many \"\"\" characters"],
];
let values_as_opts = values
.into_iter()
.map(|row| row.into_iter().map(|value| Some(value)));
let wsv = WSVWriter::new(values_as_opts)
// The default alignment is packed, but left and
// right aligned are also options in cases where
// your .wsv file will be looked at by people and
// not just machines.
.align_columns(ColumnAlignment::Left)
.to_string();
/// Output:
/// 1 2 3
/// 4 5 6
/// "My string with a "/" character"
/// "My string with many """""" characters"
println!("{}", wsv);
Iterator
这个WSVWriter的实现利用迭代器的懒评估,允许您写入巨大的文件。通过将迭代器传递给WSVWriter并使用WSVWriter提供的迭代器实现,您可以写入磁盘空间可以容纳的任意大小的文件。以一个例子来说,假设我需要以WSV格式将序列0到9的4,294,967,295(u32::MAX)行打印到我的终端。我可以使用以下代码完成这个任务:
注意:此API也只在必要时用引号包围字符串。
use whitespacesv::WSVWriter;
let values = (0..u32::MAX).map(|_| (0..10).into_iter().map(|val| Some(val.to_string())));
// NOTE: column alignment is not respected when using this iterator implementation.
for ch in WSVWriter::new(values) {
print!("{}", ch);
// This is so that my computer doesn't fry when running unit tests.
break;
}