14 个版本
0.4.2 | 2024年7月21日 |
---|---|
0.4.1 | 2023年5月20日 |
0.4.0 | 2022年11月30日 |
0.3.1 | 2022年6月27日 |
0.1.0 | 2021年3月10日 |
#106 在 解析器实现
17,263 每月下载量
用于 23 个 crate (8 个直接使用)
73KB
674 行
一个基于正则表达式的 sscanf (format!() 的逆操作) 宏的 Rust crate
sscanf
是一个 C 函数,它接受一个字符串、一个包含占位符的格式字符串和几个变量(在 Rust 版本中用类型替换)。然后它解析输入字符串,将占位符后面的值写入变量中(Rust:返回一个元组)。sscanf
可以看作是 format!()
调用的逆操作
// format: takes format string and values, returns String
let msg = format!("Hello {}{}!", "World", 5);
assert_eq!(msg, "Hello World5!");
// sscanf: takes string, format string and types, returns tuple
let parsed = sscanf::sscanf!(msg, "Hello {}{}!", str, usize);
// parsed is Result<(&str, usize), ...>
assert_eq!(parsed.unwrap(), ("World", 5));
// alternative syntax:
let parsed2 = sscanf::sscanf!(msg, "Hello {str}{usize}!");
assert_eq!(parsed2.unwrap(), ("World", 5));
sscanf!()
接受一个类似 format!()
的格式字符串,但不将值写入占位符({}
),而是将这些值提取到返回的元组中。
如果格式字符串匹配失败,则返回错误
let msg = "Text that doesn't match the format string";
let parsed = sscanf::sscanf!(msg, "Hello {str}{usize}!");
assert!(matches!(parsed, Err(sscanf::Error::MatchFailed)));
占位符中的类型
类型可以是格式字符串之后的单独参数,也可以直接在 {}
占位符内给出。
第一种方法允许在键入时自动完成、语法高亮以及更好的 sscanf 生成的编译器错误,以防提供了错误类型。
第二种模仿了自 Rust 1.58 版以来的 Rust format!() 行为。在稳定 Rust 中使用时,此选项的编译器错误较差,但其他方面与第一个选项相同。
更多 sscanf
功能的示例
use sscanf::sscanf;
use std::num::NonZeroUsize;
let input = "<x=3, y=-6, z=6>";
let parsed = sscanf!(input, "<x={i32}, y={i32}, z={i32}>");
assert_eq!(parsed.unwrap(), (3, -6, 6));
let input = "Move to N36E21";
let parsed = sscanf!(input, "Move to {char}{usize}{char}{usize}");
assert_eq!(parsed.unwrap(), ('N', 36, 'E', 21));
let input = "Escape literal { } as {{ and }}";
let parsed = sscanf!(input, "Escape literal {{ }} as {{{{ and }}}}");
assert_eq!(parsed.unwrap(), ());
let input = "Indexing types: N36E21";
let parsed = sscanf!(input, "Indexing types: {1}{0}{1}{0}", NonZeroUsize, char);
// output is in the order of the placeholders
assert_eq!(parsed.unwrap(), ('N', NonZeroUsize::new(36).unwrap(),
'E', NonZeroUsize::new(21).unwrap()));
let input = "A Sentence with Spaces. Another Sentence.";
// str and String do the same, but String clones from the input string
// to take ownership instead of borrowing.
let (a, b) = sscanf!(input, "{String}. {str}.").unwrap();
assert_eq!(a, "A Sentence with Spaces");
assert_eq!(b, "Another Sentence");
// Number format options
let input = "ab01 127 101010 1Z";
let parsed = sscanf!(input, "{usize:x} {i32:o} {u8:b} {u32:r36}");
let (a, b, c, d) = parsed.unwrap();
assert_eq!(a, 0xab01); // Hexadecimal
assert_eq!(b, 0o127); // Octal
assert_eq!(c, 0b101010); // Binary
assert_eq!(d, 71); // any radix (r36 = Radix 36)
assert_eq!(d, u32::from_str_radix("1Z", 36).unwrap());
let input = "color: #D4AF37";
// Number types take their size into account, and hexadecimal u8 can
// have at most 2 digits => only possible match is 2 digits each.
let (r, g, b) = sscanf!(input, "color: #{u8:x}{u8:x}{u8:x}").unwrap();
assert_eq!((r, g, b), (0xD4, 0xAF, 0x37));
在这个情况下,输入是一个 &'static str
,但也可以是 String
、&str
、&String
、等等。基本上,任何具有 Deref<Target=str>
并且不拥有所有权的对象。有关可能输入的几个示例,请参阅此处。
该宏的解析部分限制很少,因为它将 {}
替换为对应类型的正则表达式(regex
)。例如
char
只是一个字符(正则表达式"."
)str
是任意字符序列(正则表达式".+?"
)- 数字是任意数字序列(正则表达式
"[-+]?\d+"
)
等等。实际的数字实现会尝试考虑类型的尺寸和其他一些细节,但这只是解析的大致内容。
这意味着只要正则表达式找到合适的组合,任何替换序列都是可能的。在上面的 char, usize, char, usize
示例中,它成功将 N
和 E
分配给 char
,因为它们不能与 usize
匹配。
格式选项
所有选项都在 '{'
'}'
内部,并在 :
后面,因此可以是 {<type>:<option>}
或 {:<option>}
。注意:类型可能仍然包含包含 ::
的路径。任何双冒号都将被忽略,并且只使用单冒号来分隔选项。
过程宏没有可靠的数据类型信息,只能通过名称比较类型。这意味着以下选项仅适用于以下类型:i32
(不 是路径,例如:)或者包装器(std::i32
)或者别名(struct Wrapper(i32);
)。仅限 type Alias = i32;
i32
,usize
,u16
,...
配置 | 描述 | 可能类型 |
---|---|---|
{:/ <regex> /} |
自定义正则表达式 | 任何 |
{:x} |
十六进制数 | 整数 |
{:o} |
八进制数 | 整数 |
{:b} |
二进制数 | 整数 |
{:r2} - {:r36} |
基数2 - 基数36的数字 | 整数 |
# |
"交替"形式 | 各种类型 |
自定义正则表达式
{:/.../}
: 匹配正则表达式中的Regex
,正则表达式在/
/
之间
例如
let input = "random Text";
let parsed = sscanf::sscanf!(input, "{str:/[^m]+/}{str}");
// regex [^m]+ matches anything that isn't an 'm'
// => stops at the 'm' in 'random'
assert_eq!(parsed.unwrap(), ("rando", "m Text"));
正则表达式使用与JavaScript /
.../语法相同的转义逻辑,这意味着数字等正则表达式的正常转义(如\d
)有效,并且还需要将任何用于结束正则表达式的/
转义为\/
。
注意:由于正则表达式中使用了反斜杠,你应该使用原始字符串作为格式字符串,否则需要将每个反斜杠转义为\\
。
use sscanf::sscanf;
let input = "1234";
let parsed = sscanf!(input, r"{u8:/\d{2}/}{u8}"); // regex \d{2} matches 2 digits
let _ = sscanf!(input, "{u8:/\\d{2}/}{u8}"); // the same with a non-raw string
assert_eq!(parsed.unwrap(), (12, 34));
注意:如果你在正则表达式中使用任何未转义的括号,你必须通过在开头添加一个?:
来防止它们形成捕获组:例如,{:/..(..)../}
变为{:/..(?:..)../}
。这不会以任何方式改变它们的功能,但对于sscanf
的解析过程是必要的。
这也意味着不能在自定义类型上使用自定义正则表达式,因为这些类型依赖于它们的正则表达式内部有确切的捕获组数量。
基数选项
仅适用于原始整数类型(u8
,...,u128
,i8
,...,i128
,usize
,isize
)。
x
:十六进制数(数字0-9和a-f或A-F),可选前缀0x
或0X
o
:八进制数(数字0-7),可选前缀0o
或0O
b
:二进制数(数字0-1),可选前缀0b
或0B
r2
-r36
:任何基数数(数字0-9和a-z或A-Z,对于更高的基数)
备选形式
如果与基数选项一起使用:则使数字需要前缀(0x,0o,0b)。
关于前缀的说明:r2
,r8
和r16
分别与b
,o
和x
匹配相同的数字,但没有前缀。因此
{:x}
可能有前缀,匹配如0xab
或ab
这样的数字{:r16}
没有前缀,只会匹配ab
{:#x}
必须有前缀,仅匹配0xab
{:#r16}
会产生编译错误
未来可能会添加更多关于#
的使用。如果您有建议,请告诉我。
自定义类型
sscanf
默认与大多数来自std
的原始类型以及String
一起使用。完整的列表可以在此处查看:[RegexRepresentation
的实现]
要添加更多类型,有以下三种选择
- 为您的类型推导
FromScanf
(推荐) - 为您的类型实现
RegexRepresentation
和std::str::FromStr
- 为您的类型实现
RegexRepresentation
并手动实现FromScanf
(强烈不建议)
最简单的方法是使用derive
#[derive(sscanf::FromScanf)]
#[sscanf(format = "#{r:x}{g:x}{b:x}")] // matches '#' followed by 3 hexadecimal u8s
struct Color {
r: u8,
g: u8,
b: u8,
}
let input = "color: #ff00cc";
let parsed = sscanf::sscanf!(input, "color: {Color}").unwrap();
assert!(matches!(parsed, Color { r: 0xff, g: 0x00, b: 0xcc }));
也适用于枚举类型
#[derive(sscanf::FromScanf)]
enum HasChanged {
#[sscanf(format = "received {added} additions and {deleted} deletions")]
Yes {
added: usize,
deleted: usize,
},
#[sscanf("has not changed")] // the `format =` part can be omitted
No
}
let input = "Your file has not changed since your last visit!";
let parsed = sscanf::sscanf!(input, "Your file {HasChanged} since your last visit!").unwrap();
assert!(matches!(parsed, HasChanged::No));
let input = "Your file received 325 additions and 15 deletions since your last visit!";
let parsed = sscanf::sscanf!(input, "Your file {HasChanged} since your last visit!").unwrap();
assert!(matches!(parsed, HasChanged::Yes { added: 325, deleted: 15 }));
更多详细信息请参阅FromScanf
文档和derive
文档
变更日志
请参阅Changelog.md
许可证
根据您选择,许可协议为Apache License, Version 2.0或MIT license。
依赖项
约6MB
约104K SLoC