Skip to content

Commit 3aa0740

Browse files
committed
Use fmt::Write for serialising LispObjects
This gets rid of most of the string allocations.
1 parent 8df3b40 commit 3aa0740

File tree

1 file changed

+51
-46
lines changed

1 file changed

+51
-46
lines changed

src/bytecode.rs

Lines changed: 51 additions & 46 deletions
Original file line numberDiff line numberDiff line change
@@ -36,77 +36,82 @@ impl FromStr for LispObject {
3636
}
3737
}
3838

39-
impl LispObject {
40-
fn to_repl(&self) -> String {
39+
impl std::fmt::Display for LispObject {
40+
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
4141
match self {
42-
LispObject::Symbol(s) => s.clone(),
43-
LispObject::Keyword(s) => format!(":{}", s),
42+
LispObject::Symbol(s) => f.write_str(s),
43+
LispObject::Keyword(s) => write!(f, ":{s}"),
4444
LispObject::Str(s) => {
45-
let mut result = String::new();
46-
result.reserve(s.len() * 2 + 2);
47-
result.push('"');
45+
f.write_char('"')?;
4846
for c in s.chars() {
4947
if c == '\"' || c == '\\' {
50-
result.push('\\');
51-
result.push(c);
48+
f.write_char('\\')?;
49+
f.write_char(c)?;
5250
} else if (c as u32) < 32 || (c as u32) == 127 {
5351
// not printable
5452
// NOTE: cannot use escape for c in 128..=255, otherwise the string would become unibyte
55-
write!(result, "\\{:03o}", c as u32).unwrap();
53+
write!(f, "\\{:03o}", c as u32)?;
5654
} else {
57-
result.push(c);
55+
f.write_char(c)?;
5856
}
5957
}
60-
result.push('"');
61-
result
58+
f.write_char('"')?;
59+
Ok(())
6260
},
6361
LispObject::UnibyteStr(vec) => {
64-
let mut result = String::new();
65-
result.reserve(vec.len() * 4 + 2);
66-
result.push('"');
62+
f.write_char('"')?;
6763
let mut last_oct_escape_not_full = false;
6864
for c in vec {
6965
let mut oct_escape_not_full = false;
7066
match *c {
71-
7 => result += "\\a",
72-
8 => result += "\\b",
73-
9 => result += "\\t",
74-
10 => result += "\\n",
75-
11 => result += "\\v",
76-
12 => result += "\\f",
77-
13 => result += "\\r",
78-
127 => result += "\\d",
79-
27 => result += "\\e",
67+
7 => f.write_str("\\a")?,
68+
8 => f.write_str("\\b")?,
69+
9 => f.write_str("\\t")?,
70+
10 => f.write_str("\\n")?,
71+
11 => f.write_str("\\v")?,
72+
12 => f.write_str("\\f")?,
73+
13 => f.write_str("\\r")?,
74+
127 => f.write_str("\\d")?,
75+
27 => f.write_str("\\e")?,
8076
// NOTE: do not use 0..=7 in this branch, because it takes one more byte than the next branch
8177
8..=26 => { // \^@ \^A \^B ... \^Z
82-
write!(&mut result, "\\^{}", (*c as u32 + 64) as u8 as char).unwrap();
78+
write!(f, "\\^{}", (*c as u32 + 64) as u8 as char)?;
8379
},
8480
0..=7 | 27..=31 | 128..=255 | 34 | 92 => { // oct, for unprintable and '"' and '\\'
85-
let last_len = result.len();
86-
write!(result, "\\{:o}", *c as u32).unwrap();
87-
if result.len() - last_len < 4 {
81+
let oct_s = format!("\\{:o}", *c as u32);
82+
if oct_s.len() < 4 {
8883
oct_escape_not_full = true;
8984
}
85+
f.write_str(&oct_s)?;
9086
},
9187
_ => { // printable
9288
// https://www.gnu.org/software/emacs/manual/html_node/elisp/Non_002dASCII-in-St
9389
if last_oct_escape_not_full && ('0'..='7').contains(&(*c as char)) {
94-
result += "\\ ";
90+
f.write_str("\\ ")?;
9591
}
96-
result.push(*c as char);
92+
f.write_char(*c as char)?;
9793
},
9894
}
9995
last_oct_escape_not_full = oct_escape_not_full;
10096
}
101-
result.push('"');
102-
result
97+
f.write_char('"')
10398
},
104-
LispObject::Int(i) => i.to_string(),
105-
LispObject::Float(s) => s.clone(),
106-
LispObject::Nil => "nil".into(),
107-
LispObject::T => "t".into(),
108-
LispObject::Vector(v) =>
109-
format!("[{}]", v.iter().map(|x| x.to_repl()).collect::<Vec<_>>().join(" "))
99+
LispObject::Int(i) => write!(f, "{i}"),
100+
LispObject::Float(s) => write!(f, "{s}"),
101+
LispObject::Nil => f.write_str("nil"),
102+
LispObject::T => f.write_str("t"),
103+
LispObject::Vector(v) => {
104+
f.write_char('[')?;
105+
let mut iter = v.iter();
106+
if let Some(first) = iter.next() {
107+
write!(f, "{first}")?;
108+
for x in iter {
109+
write!(f, " {x}")?;
110+
}
111+
}
112+
f.write_char(']')?;
113+
Ok(())
114+
}
110115
}
111116
}
112117
}
@@ -424,8 +429,8 @@ impl BytecodeCompiler {
424429
fn into_repl(self) -> Result<String> {
425430
let (code, constants, max_stack_size) = self.into_bytecode()?;
426431
Ok(format!("#[0 {} {} {}]",
427-
LispObject::UnibyteStr(code).to_repl(),
428-
LispObject::Vector(constants).to_repl(),
432+
LispObject::UnibyteStr(code),
433+
LispObject::Vector(constants),
429434
max_stack_size))
430435
}
431436
}
@@ -443,9 +448,9 @@ pub fn generate_bytecode_repl(value: &json::Value, options: BytecodeOptions) ->
443448

444449
#[test]
445450
fn test_string_repl() {
446-
assert_eq!(LispObject::UnibyteStr("\x00".into()).to_repl(), r#""\0""#);
447-
assert_eq!(LispObject::UnibyteStr("\x1a".into()).to_repl(), r#""\^Z""#);
448-
assert_eq!(LispObject::UnibyteStr("\x20".into()).to_repl(), r#"" ""#);
449-
assert_eq!(LispObject::UnibyteStr("\x7f".into()).to_repl(), r#""\d""#);
450-
assert_eq!(LispObject::UnibyteStr(vec![0xff]).to_repl(), r#""\377""#);
451+
assert_eq!(format!("{}", LispObject::UnibyteStr("\x00".into())), r#""\0""#);
452+
assert_eq!(format!("{}", LispObject::UnibyteStr("\x1a".into())), r#""\^Z""#);
453+
assert_eq!(format!("{}", LispObject::UnibyteStr("\x20".into())), r#"" ""#);
454+
assert_eq!(format!("{}", LispObject::UnibyteStr("\x7f".into())), r#""\d""#);
455+
assert_eq!(format!("{}", LispObject::UnibyteStr(vec![0xff])), r#""\377""#);
451456
}

0 commit comments

Comments
 (0)