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解析器实现

Download history 3854/week @ 2024-05-01 3586/week @ 2024-05-08 4126/week @ 2024-05-15 3128/week @ 2024-05-22 3365/week @ 2024-05-29 3296/week @ 2024-06-05 4346/week @ 2024-06-12 4149/week @ 2024-06-19 5284/week @ 2024-06-26 2969/week @ 2024-07-03 3915/week @ 2024-07-10 4963/week @ 2024-07-17 4269/week @ 2024-07-24 3831/week @ 2024-07-31 4405/week @ 2024-08-07 3692/week @ 2024-08-14

17,263 每月下载量
用于 23 个 crate (8 个直接使用)

MIT/Apache

73KB
674

一个基于正则表达式的 sscanf (format!() 的逆操作) 宏的 Rust crate

Tests Crates.io Documentation Dependency status

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 示例中,它成功将 NE 分配给 char,因为它们不能与 usize 匹配。

格式选项

所有选项都在 '{' '}' 内部,并在 : 后面,因此可以是 {<type>:<option>}{:<option>}。注意:类型可能仍然包含包含 :: 的路径。任何双冒号都将被忽略,并且只使用单冒号来分隔选项。

过程宏没有可靠的数据类型信息,只能通过名称比较类型。这意味着以下选项仅适用于以下类型:i32 是路径,例如:std::i32)或者包装器(struct Wrapper(i32);)或者别名(type Alias = i32;)。仅限 i32usizeu16,...

配置 描述 可能类型
{:/ <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,...,u128i8,...,i128usizeisize)。

  • x:十六进制数(数字0-9和a-f或A-F),可选前缀0x0X
  • o:八进制数(数字0-7),可选前缀0o0O
  • b:二进制数(数字0-1),可选前缀0b0B
  • r2 - r36:任何基数数(数字0-9和a-z或A-Z,对于更高的基数)

备选形式

如果与基数选项一起使用:则使数字需要前缀(0x,0o,0b)。

关于前缀的说明:r2r8r16分别与box匹配相同的数字,但没有前缀。因此

  • {:x} 可能有前缀,匹配如0xabab这样的数字
  • {:r16}没有前缀,只会匹配ab
  • {:#x} 必须有前缀,仅匹配0xab
  • {:#r16}会产生编译错误

未来可能会添加更多关于#的使用。如果您有建议,请告诉我。

自定义类型

sscanf默认与大多数来自std的原始类型以及String一起使用。完整的列表可以在此处查看:[RegexRepresentation的实现]

要添加更多类型,有以下三种选择

  • 为您的类型推导FromScanf(推荐)
  • 为您的类型实现RegexRepresentationstd::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.0MIT license

依赖项

约6MB
约104K SLoC