#字符串格式化 #Lisp #指令 #参数 #控制 #循环 #

nightly cl-format

在 Rust 中使用 Common Lisp 格式

15 个版本

新版本 0.2.6 2024 年 8 月 21 日
0.2.5 2024 年 8 月 6 日
0.2.3 2024 年 3 月 14 日
0.2.1 2023 年 8 月 26 日
0.1.2 2023 年 4 月 9 日

#176 in Rust 模式

Download history 21/week @ 2024-07-25 227/week @ 2024-08-01 24/week @ 2024-08-08

每月 272 次下载

MIT 许可证

140KB
3.5K SLoC

cl-format

Crates.io

cl-format 是 Common Lisp format 函数的 Rust 实现。

TL;DR:使用多个指令如 ~a ~{~} 和灵活的条件和循环控制字符串来从参数格式化字符串。

如果您不熟悉 Common Lisp,以下是一些页面,让您有一个大致的了解:

注意:我还没有实现所有格式指令。我正在努力实现。请见下文以查找已完成的项目。

顺便说一句:我正在尝试复制 Common Lisp 的格式函数的行为,但可能有一些妥协。

用法

使用此库有两种方式。您可以使用 cl_format! 宏,或者生成控制字符串并自己格式化参数以获得更多灵活性。

首先,将 cl-format = "0.2" 添加到您的 Cargo.toml 中。

使用宏

~a 是我最喜欢使用的最常见的指令,所以让我们从正常的 ~a 开始。

let a = cl_format!("~a, ~a, ~a", &1_i32, &2, &3);
assert_eq!(String::from("1, 2, 3"), a.unwrap());

所有用于格式化的参数都必须是借用,并且它们必须实现 TildeAble 特性。有关更多详细信息,请参阅 为自定义类型实现 部分。

以下是宏的更多用法。转义字符串中的双引号符号

let s = String::from("abc");
let a = cl_format!("~a, ~a, ~a, ~S", &1_i32, &2, &3, &s);
assert_eq!(String::from("1, 2, 3, \"abc\""), a.unwrap());

或者不转义

let a = cl_format!("start ~a, ~a, ~a, ~a, here", &1_i32, &2, &3, &s);
assert_eq!(String::from("start 1, 2, 3, abc, here"), a.unwrap());

让我们在控制字符串中创建一些像 Lispers 一样做的循环

let ll: Vec<&dyn TildeAble> = vec![&1, &2, &3];
let a = cl_format!("~a, ~a, ~a, ~{~a,~}", &1_i32, &2, &3, &ll);
assert_eq!(String::from("1, 2, 3, 1,2,3,"), a.unwrap());

等等,我们在结果末尾有一个不必要的逗号,让我们清理一下

let a = cl_format!("~a, ~a, ~a, ~{~a~^,~}", &1_i32, &2, &3, &ll);
assert_eq!(String::from("1, 2, 3, 1,2,3"), a.unwrap());

我突然不想再循环Vec了

let l = vec![&1 as &dyn TildeAble, &2, &3];
let a = cl_format!("The value is:\n ~a", &l);
assert_eq!(String::from("The value is:\n [1, 2, 3]"), a.unwrap());

现在,Common Lisp和Rust之间有些不一致。在Common Lisp中,控制字符串中的~%代表换行,但我们现在在Rust中,所以\n将起作用。

我觉得把类型展示为&dyn TildeAble给Vec中的元素看有点累了。但我还没有找到避免它的方法。如果你知道,请告诉我。所以我添加了一些宏

let l = vec![tilde!(&1), &2, &3];
let a = cl_format!("The value is:\n ~a", &l);
assert_eq!(String::from("The value is:\n [1, 2, 3]"), a.unwrap());

与Common Lisp一样,我们可以遍历所有参数,而不是将它们放入Vec中

let a = cl_format!("~@{~a~^, ~}", &1, &2, &3);
assert_eq!(String::from("1, 2, 3"), a.unwrap());

现在,让我们尝试一些条件控制(你可以在A Few FORMAT RecipesConditional Formatting章节中找到条件控制字符串的含义)

let l = vec![tilde!(&1), &2, &3];
let a = cl_format!("~{~a~#[~;, and ~:;, ~]~}", &l);
assert_eq!(String::from("1, 2, and 3"), a.unwrap());

let l = vec![tilde!(&1), &2, &3, &4];
let a = cl_format!("~{~a~#[~;, and ~:;, ~]~}", &l);
assert_eq!(String::from("1, 2, 3, and 4"), a.unwrap());

手动

使用宏每次都会生成控制字符串实例。如果你试图在所有地方使用控制字符串,因为它足够灵活,可以用于多种用途,这可能会造成浪费。

我们可以自己生成它

let cs = cl_format::ControlStr::from("~{~#[~;~a~;~a and ~a~:;~@{~a~#[~;, and ~:;, ~]~}~]~}").unwrap();

然后我们可以生成控制字符串的Args以显示

let mut list = vec![];
let args = Args::new(vec![&list]);

让我们通过提供不同长度的参数来多次使用它

// this equal cl_format!(cs, &list)
assert_eq!(cs.reveal(args).unwrap(), "".to_string());

list.push(&1);
let args = Args::new(vec![&list]);
assert_eq!(cs.reveal(args).unwrap(), "1".to_string());

list.push(&2);
let args = Args::new(vec![&list]);
assert_eq!(cs.reveal(args).unwrap(), "1 and 2".to_string());

list.push(&3);
let args = Args::new(vec![&list]);
assert_eq!(cs.reveal(args).unwrap(), "1, 2, and 3".to_string());

list.push(&4);
let args = Args::new(vec![&list]);
assert_eq!(cs.reveal(args).unwrap(), "1, 2, 3, and 4".to_string());

让我们尝试一个混合示例


let my_team = String::from("STeam");
let my_stars = vec![
    String::from("Adam Lambert"),
    String::from("Queen"),
    String::from("snoop dogg"),
];

let stars = my_stars
    .iter()
    .map(|s| tilde!(s))
    .collect::<Vec<&dyn TildeAble>>();
	
assert_eq!(
    String::from("my favorite team \"STeam\" will win the superbowl LVIII. And Adam Lambert, Queen, and snoop dogg will in half time show. And the scores should be 38:35"),
    cl_format!(
        "my favorite team ~S will win the superbowl ~@R. And ~{~#[~;~a~;~a and ~a~:;~@{~a~#[~;, and ~:;, ~]~}~]~} will in half time show. And the scores should be ~d:~d",
        &my_team,
        &58,
        &stars,
        &38,
        &35
    )
    .unwrap()
);

为自定义类型实现

到目前为止,我们只展示了基本类型。如果能让我们自己的类型也能被揭示,那就更好了。

这里有一个演示如何实现的示例

use cl_format::*;

// has to derive to Debug
#[derive(Debug)]
struct MyStruct {
    a: usize,
    b: String,
}

impl TildeAble for MyStruct {
	// there are a lot methods inside, but not every of them
	// we are need.
	
	// ~a is good enough
	fn into_tildekind_va(&self) -> Option<&dyn TildeKindVa> {
        Some(self)
    }
	
	// ~d just for show case
	fn into_tildekind_digit(&self) -> Option<&dyn TildeKindDigit> {
        Some(self)
    }
	
	// how many elements you want cl_format treat this type
	// 1 is enough. And this one has to implement
	fn len(&self) -> usize {
        1
    }
}

到目前为止,你的IDE应该会给你一些错误,让你实现TildeKindVaTildeKindDigit

impl TildeKindVa for MyStruct {
    fn format(&self, tkind: &TildeKind, buf: &mut String) -> Result<(), TildeError> {
        buf.push_str(&format!("a: {}, b: {}", self.a, self.b));
        Ok(())
    }
}

impl TildeKindDigit for MyStruct {
    fn format(&self, tkind: &TildeKind, buf: &mut String) -> Result<(), TildeError> {
        buf.push_str(&format!("{}", self.a));
        Ok(())
    }
}

现在MyStruct可以用cl_format使用,但正如你所猜测的,只有对于~a~d

let s = MyStruct {
    a: 1,
    b: "b".to_string(),
};

assert_eq!("a: 1, b: b".to_string(), cl_format!("~a", &s).unwrap());
assert_eq!(
    "a: 1, b: b lalalal a: 1, b: b".to_string(),
    cl_format!("~a lalalal ~a", &s, &s).unwrap()
);

assert_eq!("1".to_string(), cl_format!("~d", &s).unwrap());
assert_eq!(
    "First: a: 1, b: b; Second: 1".to_string(),
    cl_format!("First: ~a; Second: ~d", &s, &s).unwrap()
);

格式指令

这是实现过的指令表

波浪号 Rust类型
~a f32, f64, char, i8, i16, i32, i64, i128, isize, bool, u8, u16, u32, u64, u128, usize, String
~s f32, f64, char, i32, i64, usize, bool, u32, u64, String
~d i8, i16, i32, i64, i128, u8, u16, u32, u64, u128, usize, isize
~C char
~[~](正常条件) bool, usize
~R i8, i16, i32, i64, i128, u8, u16, u32, u64, u128, usize, isize

依赖关系

~300–750KB
~18K SLoC