1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
use std::fmt;
use std::fmt::Write;
use std::rc::Rc;
use crate::object::Object;
enum NumberFormat {
Boolean,
None,
Octal,
Hex,
HexaDecimal,
}
enum SpecJustify {
Default,
Left,
Right,
}
pub struct Collector(pub Vec<String>);
impl fmt::Write for Collector {
fn write_str(&mut self, s: &str) -> fmt::Result {
self.0.push(s.to_string());
Ok(())
}
}
fn format_obj(
collector: &mut Collector,
padding: &str,
justify: SpecJustify,
width_str: &str,
num_fmt: NumberFormat,
obj: &Object,
) -> Result<(), String> {
// Parse width
let width: usize = if width_str.is_empty() {
0
} else {
width_str
.parse()
.map_err(|_: std::num::ParseIntError| "Failed to parse width".to_string())?
};
// Format based on NumberFormat
let formatted = match num_fmt {
NumberFormat::Boolean => {
if let Object::Integer(num) = obj {
format!("{:b}", *num as usize)
} else {
Err(String::from("Can't format non-number as binary"))?
}
}
NumberFormat::Octal => {
if let Object::Integer(num) = obj {
format!("{:o}", *num as usize)
} else {
Err(String::from("Can't format non-number as octal"))?
}
}
NumberFormat::Hex => {
if let Object::Integer(num) = obj {
format!("{:x}", *num as usize)
} else {
Err(String::from("Can't format non-number as hex"))?
}
}
NumberFormat::HexaDecimal => {
if let Object::Integer(num) = obj {
format!("{:X}", *num as usize)
} else {
Err(String::from("Can't format non-number as hex"))?
}
}
NumberFormat::None => {
match obj {
Object::Str(t) => {
// Do not use display trait to print the string as
// it wraps the string in quotes
t.to_string()
}
o => {
format!("{}", o)
}
}
}
};
// Use a default padding of spaces
let padding = if padding.is_empty() { " " } else { padding };
let width_pad = width.saturating_sub(formatted.len());
let padded: String = padding.repeat(width_pad);
// Use default justification as right for number and left for everything else
let justify = match justify {
SpecJustify::Default => {
if let Object::Integer(_) = obj {
SpecJustify::Right
} else {
SpecJustify::Left
}
}
_ => justify,
};
// Handle justification and padding
let justify = match justify {
SpecJustify::Default => {
if let Object::Integer(_) = obj {
SpecJustify::Right
} else {
SpecJustify::Left
}
}
_ => justify,
};
// Handle justification and padding
let formatted_output = match justify {
SpecJustify::Left => format!("{}{}", formatted, padded),
SpecJustify::Right => format!("{}{}", padded, formatted),
_ => formatted,
};
write!(collector, "{}", formatted_output).map_err(|e| e.to_string())?;
Ok(())
}
pub fn format_buf(args: Vec<Rc<Object>>) -> Result<Collector, String> {
if args.is_empty() {
return Err(String::from("takes a minimum of one argument"));
}
let parts: Vec<char> = if let Object::Str(fmt) = &*args[0] {
fmt.chars().collect()
} else {
return Err(String::from("Expected a string or format specifier"));
};
// Create a custom writer that collects the formatted output
let mut collector = Collector(Vec::new());
let mut idx_fmt = 0; // index to format specifier
let mut idx_arg = 1; // index to args skipping the format specifier
let mut in_spec = false; // inside '{}'
let mut in_spec_format = false; // format specifier available after colon ':'
let mut curr_spec_just = SpecJustify::Default;
let mut curr_spec_width = String::new(); // store format specifier
let mut curr_spec_padding = String::new(); // store padding specifier before '<' or '>'
let mut curr_spec_idx = String::new(); // store index specifier {0}, {1}, etc
let mut num_fmt: NumberFormat = NumberFormat::None;
while idx_fmt < parts.len() {
let curr = parts[idx_fmt];
let next = if idx_fmt < parts.len() - 1 {
parts[idx_fmt + 1]
} else {
'\0'
};
if curr == '{' {
if next == '{' {
write!(collector, "{{").map_err(|e| e.to_string())?;
idx_fmt += 2; // skip next brace as well
} else {
in_spec = true;
idx_fmt += 1;
}
continue;
} else if curr == '}' {
if next == '}' {
write!(collector, "}}").map_err(|e| e.to_string())?;
idx_fmt += 2; // skip next brace as well
continue;
}
// Now print the arguments based on the specifier
if idx_arg >= args.len() {
return Err(String::from("positional arguments exceeded the count"));
}
// If index specifier is empty, use positional index 'idx_arg'
// Otherwise, use the specified index into the arguments list
if curr_spec_idx.is_empty() {
// specifiers such as '{}', '{:10}', '{<5}', '{:0>5}' etc
format_obj(
&mut collector,
&curr_spec_padding,
curr_spec_just,
&curr_spec_width,
num_fmt,
&args[idx_arg],
)?;
idx_arg += 1;
} else {
// specifiers such as '{0}', '{1}', '{0:10}', '{0<5}', '{1:0>5}' etc
// 'args' also includes the format specifier. 'args.len()'
// So, 'idx_print' should be the next element in args vector.
let idx_print = curr_spec_idx.parse::<usize>().map_err(|e| e.to_string())? + 1;
if idx_print >= args.len() {
return Err(String::from("positional argument index exceeded the count"));
}
format_obj(
&mut collector,
&curr_spec_padding,
curr_spec_just,
&curr_spec_width,
num_fmt,
&args[idx_print],
)?;
}
in_spec = false;
in_spec_format = false;
curr_spec_just = SpecJustify::Default;
curr_spec_width = String::new();
curr_spec_padding = String::new();
curr_spec_idx = String::new();
num_fmt = NumberFormat::None;
idx_fmt += 1;
continue;
}
if in_spec {
if curr == ':' {
in_spec_format = true;
idx_fmt += 1;
continue;
}
if in_spec_format && (curr == '<' || curr == '>') {
// the specifier is actually a padding one
curr_spec_padding = curr_spec_width.clone();
curr_spec_width = String::new();
idx_fmt += 1;
curr_spec_just = if curr == '<' {
SpecJustify::Left
} else {
SpecJustify::Right
};
continue;
}
num_fmt = match curr {
'b' => NumberFormat::Boolean,
'o' => NumberFormat::Octal,
'x' => NumberFormat::Hex,
'X' => NumberFormat::HexaDecimal,
_ => {
// If none of the numberic specifiers, it must be an int
if in_spec_format {
// Specifiers such as {:10}, {:0<5}, {:0>5}
curr_spec_width.push(curr);
} else {
// Specifiers such as {0}
curr_spec_idx.push(curr);
}
NumberFormat::None
}
};
} else {
// Treat characters outside specifier as printable ones
write!(collector, "{}", curr).map_err(|e| e.to_string())?;
}
idx_fmt += 1;
}
Ok(collector)
}