bock_core/
string_builder.rs1use std::sync::{Arc, Mutex};
7
8use bock_interp::{BockString, BuiltinRegistry, RuntimeError, TypeTag, Value};
9
10pub fn register(registry: &mut BuiltinRegistry) {
12 registry.register_global("StringBuilder", sb_new);
14
15 registry.register(TypeTag::StringBuilder, "append", sb_append);
17 registry.register(TypeTag::StringBuilder, "len", sb_len);
18 registry.register(TypeTag::StringBuilder, "build", sb_build);
19 registry.register(TypeTag::StringBuilder, "clear", sb_clear);
20}
21
22fn expect_sb(args: &[Value], method: &str) -> Result<Arc<Mutex<String>>, RuntimeError> {
23 match args.first() {
24 Some(Value::StringBuilder(rc)) => Ok(Arc::clone(rc)),
25 Some(other) => Err(RuntimeError::TypeError(format!(
26 "StringBuilder.{method} called on {other}, expected StringBuilder"
27 ))),
28 None => Err(RuntimeError::ArityMismatch {
29 expected: 1,
30 got: 0,
31 }),
32 }
33}
34
35fn sb_new(args: &[Value]) -> Result<Value, RuntimeError> {
38 let initial = match args.first() {
39 Some(Value::String(s)) => s.as_str().to_string(),
40 Some(other) => {
41 return Err(RuntimeError::TypeError(format!(
42 "StringBuilder expects optional String argument, got {other}"
43 )))
44 }
45 None => String::new(),
46 };
47 Ok(Value::StringBuilder(Arc::new(Mutex::new(initial))))
48}
49
50fn sb_append(args: &[Value]) -> Result<Value, RuntimeError> {
52 let rc = expect_sb(args, "append")?;
53 let val = args.get(1).ok_or(RuntimeError::ArityMismatch {
54 expected: 2,
55 got: 1,
56 })?;
57 let s = match val {
58 Value::String(s) => s.as_str().to_string(),
59 other => other.to_string(),
60 };
61 rc.lock().unwrap().push_str(&s);
62 Ok(Value::StringBuilder(rc))
63}
64
65fn sb_len(args: &[Value]) -> Result<Value, RuntimeError> {
67 let rc = expect_sb(args, "len")?;
68 let count = rc.lock().unwrap().chars().count() as i64;
69 Ok(Value::Int(count))
70}
71
72fn sb_build(args: &[Value]) -> Result<Value, RuntimeError> {
74 let rc = expect_sb(args, "build")?;
75 let content = rc.lock().unwrap().clone();
76 Ok(Value::String(BockString::new(content)))
77}
78
79fn sb_clear(args: &[Value]) -> Result<Value, RuntimeError> {
81 let rc = expect_sb(args, "clear")?;
82 rc.lock().unwrap().clear();
83 Ok(Value::StringBuilder(rc))
84}
85
86#[cfg(test)]
87mod tests {
88 use super::*;
89
90 fn reg() -> BuiltinRegistry {
91 let mut r = BuiltinRegistry::new();
92 register(&mut r);
93 r
94 }
95
96 fn s(v: &str) -> Value {
97 Value::String(BockString::new(v))
98 }
99
100 #[test]
101 fn new_empty() {
102 let r = reg();
103 let sb = r.call_global("StringBuilder", &[]).unwrap().unwrap();
104 let result = r
105 .call(TypeTag::StringBuilder, "build", &[sb])
106 .unwrap()
107 .unwrap();
108 assert_eq!(result, s(""));
109 }
110
111 #[test]
112 fn new_with_initial() {
113 let r = reg();
114 let sb = r
115 .call_global("StringBuilder", &[s("hello")])
116 .unwrap()
117 .unwrap();
118 let result = r
119 .call(TypeTag::StringBuilder, "build", &[sb])
120 .unwrap()
121 .unwrap();
122 assert_eq!(result, s("hello"));
123 }
124
125 #[test]
126 fn append_and_build() {
127 let r = reg();
128 let sb = r.call_global("StringBuilder", &[]).unwrap().unwrap();
129 let sb = r
130 .call(TypeTag::StringBuilder, "append", &[sb, s("hello")])
131 .unwrap()
132 .unwrap();
133 let sb = r
134 .call(TypeTag::StringBuilder, "append", &[sb, s(" world")])
135 .unwrap()
136 .unwrap();
137 let result = r
138 .call(TypeTag::StringBuilder, "build", &[sb])
139 .unwrap()
140 .unwrap();
141 assert_eq!(result, s("hello world"));
142 }
143
144 #[test]
145 fn append_non_string() {
146 let r = reg();
147 let sb = r.call_global("StringBuilder", &[]).unwrap().unwrap();
148 let sb = r
149 .call(TypeTag::StringBuilder, "append", &[sb, Value::Int(42)])
150 .unwrap()
151 .unwrap();
152 let result = r
153 .call(TypeTag::StringBuilder, "build", &[sb])
154 .unwrap()
155 .unwrap();
156 assert_eq!(result, s("42"));
157 }
158
159 #[test]
160 fn len_counts_chars() {
161 let r = reg();
162 let sb = r
163 .call_global("StringBuilder", &[s("héllo")])
164 .unwrap()
165 .unwrap();
166 let result = r
167 .call(TypeTag::StringBuilder, "len", &[sb])
168 .unwrap()
169 .unwrap();
170 assert_eq!(result, Value::Int(5));
171 }
172
173 #[test]
174 fn clear_empties_buffer() {
175 let r = reg();
176 let sb = r
177 .call_global("StringBuilder", &[s("content")])
178 .unwrap()
179 .unwrap();
180 let sb = r
181 .call(TypeTag::StringBuilder, "clear", &[sb])
182 .unwrap()
183 .unwrap();
184 let result = r
185 .call(TypeTag::StringBuilder, "build", &[sb])
186 .unwrap()
187 .unwrap();
188 assert_eq!(result, s(""));
189 }
190}