1#[derive(Debug, Clone, PartialEq)]
18pub enum FormatValue {
19 None,
21 Bool(bool),
22 Int(i64),
23 Float(f64),
24 Str(String),
25}
26
27impl From<&str> for FormatValue {
28 fn from(value: &str) -> Self {
29 FormatValue::Str(value.to_string())
30 }
31}
32
33impl From<String> for FormatValue {
34 fn from(value: String) -> Self {
35 FormatValue::Str(value)
36 }
37}
38
39impl From<i64> for FormatValue {
40 fn from(value: i64) -> Self {
41 FormatValue::Int(value)
42 }
43}
44
45impl From<i32> for FormatValue {
46 fn from(value: i32) -> Self {
47 FormatValue::Int(i64::from(value))
48 }
49}
50
51impl From<usize> for FormatValue {
52 fn from(value: usize) -> Self {
53 FormatValue::Int(value as i64)
54 }
55}
56
57impl From<f64> for FormatValue {
58 fn from(value: f64) -> Self {
59 FormatValue::Float(value)
60 }
61}
62
63impl From<bool> for FormatValue {
64 fn from(value: bool) -> Self {
65 FormatValue::Bool(value)
66 }
67}
68
69impl<T: Into<FormatValue>> From<Option<T>> for FormatValue {
70 fn from(value: Option<T>) -> Self {
71 value.map_or(FormatValue::None, Into::into)
72 }
73}
74
75pub fn format(template: &str, lookup: impl Fn(&str) -> Option<FormatValue>) -> String {
81 let mut out = String::with_capacity(template.len());
82 let mut chars = template.char_indices().peekable();
83 while let Some((index, c)) = chars.next() {
84 match c {
85 '{' if chars.peek().map(|&(_, n)| n) == Some('{') => {
86 chars.next();
87 out.push('{');
88 }
89 '}' if chars.peek().map(|&(_, n)| n) == Some('}') => {
90 chars.next();
91 out.push('}');
92 }
93 '{' => {
94 let Some(close) = template[index..].find('}') else {
95 out.push_str(&template[index..]);
96 break;
97 };
98 let field = &template[index + 1..index + close];
99 while let Some(&(at, _)) = chars.peek() {
101 if at > index + close {
102 break;
103 }
104 chars.next();
105 }
106 let (name, spec) = match field.split_once(':') {
107 Some((name, spec)) => (name, spec),
108 None => (field, ""),
109 };
110 let name = name.split_once('!').map_or(name, |(name, _)| name);
112 match lookup(name).and_then(|value| format_value(&value, spec)) {
113 Some(text) => out.push_str(&text),
114 None => out.push_str(&template[index..=index + close]),
115 }
116 }
117 other => out.push(other),
118 }
119 }
120 out
121}
122
123pub fn float_repr(value: f64) -> String {
126 if value.is_nan() {
127 return "nan".to_string();
128 }
129 if value.is_infinite() {
130 return if value > 0.0 { "inf" } else { "-inf" }.to_string();
131 }
132 let sci = format!("{value:e}");
133 let (mantissa, exponent) = sci.split_once('e').expect("LowerExp has an exponent");
134 let exponent: i32 = exponent.parse().expect("integer exponent");
135 let negative = mantissa.starts_with('-');
136 let digits: String = mantissa.chars().filter(char::is_ascii_digit).collect();
137 let sign = if negative { "-" } else { "" };
138 if !(-4..16).contains(&exponent) {
139 let (head, tail) = digits.split_at(1);
140 let fraction = if tail.is_empty() {
141 String::new()
142 } else {
143 format!(".{tail}")
144 };
145 return format!(
146 "{sign}{head}{fraction}e{}{:02}",
147 exp_sign(exponent),
148 exponent.abs()
149 );
150 }
151 let point = exponent + 1;
152 let text = if point <= 0 {
153 format!("0.{}{digits}", "0".repeat((-point) as usize))
154 } else if point as usize >= digits.len() {
155 format!("{digits}{}.0", "0".repeat(point as usize - digits.len()))
156 } else {
157 let (integer, fraction) = digits.split_at(point as usize);
158 format!("{integer}.{fraction}")
159 };
160 format!("{sign}{text}")
161}
162
163fn exp_sign(exponent: i32) -> char {
164 if exponent < 0 {
165 '-'
166 } else {
167 '+'
168 }
169}
170
171#[derive(Debug, Clone, Copy, PartialEq, Eq)]
173enum Thousands {
174 None,
175 Comma,
177 Underscore,
179 UnderscoreFour,
181}
182
183#[derive(Debug)]
186struct Spec {
187 fill: char,
188 align: char,
189 sign: Option<char>,
190 no_neg_0: bool,
191 alternate: bool,
192 width: Option<usize>,
193 thousands: Thousands,
194 precision: Option<usize>,
195 kind: char,
197}
198
199fn parse_spec(spec: &str, default_type: char, default_align: char) -> Option<Spec> {
202 let chars: Vec<char> = spec.chars().collect();
203 let mut at = 0;
204 let mut parsed = Spec {
205 fill: ' ',
206 align: default_align,
207 sign: None,
208 no_neg_0: false,
209 alternate: false,
210 width: None,
211 thousands: Thousands::None,
212 precision: None,
213 kind: default_type,
214 };
215 let is_align = |c: char| matches!(c, '<' | '>' | '^' | '=');
216 let mut fill_specified = false;
217 let mut align_specified = false;
218 if chars.len() >= 2 && is_align(chars[1]) {
219 parsed.fill = chars[0];
220 parsed.align = chars[1];
221 fill_specified = true;
222 align_specified = true;
223 at = 2;
224 } else if !chars.is_empty() && is_align(chars[0]) {
225 parsed.align = chars[0];
226 align_specified = true;
227 at = 1;
228 }
229 if let Some(&c) = chars.get(at).filter(|c| matches!(c, '+' | '-' | ' ')) {
230 parsed.sign = Some(c);
231 at += 1;
232 }
233 if chars.get(at) == Some(&'z') {
234 parsed.no_neg_0 = true;
235 at += 1;
236 }
237 if chars.get(at) == Some(&'#') {
238 parsed.alternate = true;
239 at += 1;
240 }
241 if !fill_specified && chars.get(at) == Some(&'0') {
243 parsed.fill = '0';
244 if !align_specified && default_align == '>' {
245 parsed.align = '=';
246 }
247 at += 1;
248 }
249 let start = at;
250 while chars.get(at).is_some_and(char::is_ascii_digit) {
251 at += 1;
252 }
253 if at > start {
254 parsed.width = Some(chars[start..at].iter().collect::<String>().parse().ok()?);
255 }
256 if chars.get(at) == Some(&',') {
257 parsed.thousands = Thousands::Comma;
258 at += 1;
259 }
260 if chars.get(at) == Some(&'_') {
261 if parsed.thousands != Thousands::None {
262 return None; }
264 parsed.thousands = Thousands::Underscore;
265 at += 1;
266 }
267 if chars.get(at) == Some(&',') {
268 return None; }
270 if chars.get(at) == Some(&'.') {
271 at += 1;
272 let start = at;
273 while chars.get(at).is_some_and(char::is_ascii_digit) {
274 at += 1;
275 }
276 if at == start {
277 return None; }
279 parsed.precision = Some(chars[start..at].iter().collect::<String>().parse().ok()?);
280 }
281 match chars.len() - at {
282 0 => {}
283 1 => parsed.kind = chars[at],
284 _ => return None, }
286 if parsed.thousands != Thousands::None {
287 match parsed.kind {
288 'd' | 'e' | 'f' | 'g' | 'E' | 'G' | '%' | 'F' | '\0' => {}
289 'b' | 'o' | 'x' | 'X' if parsed.thousands == Thousands::Underscore => {
290 parsed.thousands = Thousands::UnderscoreFour;
291 }
292 _ => return None, }
294 }
295 Some(parsed)
296}
297
298pub fn format_value(value: &FormatValue, spec: &str) -> Option<String> {
300 if spec.is_empty() {
302 return Some(match value {
303 FormatValue::None => "None".to_string(),
304 FormatValue::Bool(flag) => if *flag { "True" } else { "False" }.to_string(),
305 FormatValue::Int(number) => number.to_string(),
306 FormatValue::Float(number) => float_repr(*number),
307 FormatValue::Str(text) => text.clone(),
308 });
309 }
310 match value {
311 FormatValue::None => None,
313 FormatValue::Str(text) => format_str(text, &parse_spec(spec, 's', '<')?),
314 FormatValue::Bool(flag) => format_int(i64::from(*flag), &parse_spec(spec, 'd', '>')?),
316 FormatValue::Int(number) => format_int(*number, &parse_spec(spec, 'd', '>')?),
317 FormatValue::Float(number) => format_float(*number, &parse_spec(spec, '\0', '>')?),
318 }
319}
320
321fn format_str(text: &str, spec: &Spec) -> Option<String> {
323 if spec.kind != 's'
324 || spec.sign.is_some()
325 || spec.no_neg_0
326 || spec.alternate
327 || spec.align == '='
328 {
329 return None;
330 }
331 let body: String = match spec.precision {
332 Some(precision) => text.chars().take(precision).collect(),
333 None => text.to_string(),
334 };
335 let length = body.chars().count();
336 let gap = spec.width.map_or(0, |width| width.saturating_sub(length));
337 let left = match spec.align {
338 '>' => gap,
339 '^' => gap / 2,
340 _ => 0,
341 };
342 let fill = |count: usize| spec.fill.to_string().repeat(count);
343 Some(format!("{}{body}{}", fill(left), fill(gap - left)))
344}
345
346fn format_int(number: i64, spec: &Spec) -> Option<String> {
349 if matches!(spec.kind, 'e' | 'E' | 'f' | 'F' | 'g' | 'G' | '%') {
350 return format_float(number as f64, spec);
351 }
352 if spec.precision.is_some() || spec.no_neg_0 {
353 return None;
354 }
355 let sign_char = number < 0;
356 let magnitude = number.unsigned_abs();
357 let (prefix, digits, remainder) = match spec.kind {
358 'c' => {
359 if spec.sign.is_some() || spec.alternate {
360 return None;
361 }
362 let c = u32::try_from(number).ok().and_then(char::from_u32)?;
365 return Some(fill_number(
368 false,
369 "",
370 "",
371 false,
372 &c.to_string(),
373 spec,
374 None,
375 ));
376 }
377 'd' | 'n' => ("", magnitude.to_string(), ""),
378 'b' => ("0b", format!("{magnitude:b}"), ""),
379 'o' => ("0o", format!("{magnitude:o}"), ""),
380 'x' => ("0x", format!("{magnitude:x}"), ""),
381 'X' => ("0X", format!("{magnitude:X}"), ""),
382 _ => return None,
383 };
384 let prefix = if spec.alternate { prefix } else { "" };
385 Some(fill_number(
386 sign_char,
387 prefix,
388 &digits,
389 false,
390 remainder,
391 spec,
392 grouping(spec),
393 ))
394}
395
396fn grouping(spec: &Spec) -> Option<(char, usize)> {
400 if spec.kind == 'n' {
401 return None;
402 }
403 match spec.thousands {
404 Thousands::None => None,
405 Thousands::Comma => Some((',', 3)),
406 Thousands::Underscore => Some(('_', 3)),
407 Thousands::UnderscoreFour => Some(('_', 4)),
408 }
409}
410
411fn format_float(number: f64, spec: &Spec) -> Option<String> {
413 let mut kind = spec.kind;
414 let mut add_dot_0 = false;
415 let mut default_precision = 6;
416 if kind == '\0' {
417 add_dot_0 = true;
420 kind = 'r';
421 default_precision = 0;
422 }
423 if kind == 'n' {
424 kind = 'g';
425 }
426 if !matches!(kind, 'e' | 'E' | 'f' | 'F' | 'g' | 'G' | '%' | 'r') {
427 return None; }
429 let mut value = number;
430 let mut add_pct = false;
431 if kind == '%' {
432 kind = 'f';
433 value *= 100.0;
434 add_pct = true;
435 }
436 let precision = match spec.precision {
437 None => default_precision,
438 Some(precision) => {
439 if kind == 'r' {
440 kind = 'g';
441 }
442 precision
443 }
444 };
445 let mut buffer = double_to_string(
446 value,
447 kind,
448 precision,
449 spec.alternate,
450 add_dot_0,
451 spec.no_neg_0,
452 );
453 if add_pct {
454 buffer.push('%');
455 }
456 let (negative, body) = match buffer.strip_prefix('-') {
457 Some(body) => (true, body),
458 None => (false, buffer.as_str()),
459 };
460 let digit_count = body.bytes().take_while(u8::is_ascii_digit).count();
463 let (digits, rest) = body.split_at(digit_count);
464 let (has_decimal, remainder) = match rest.strip_prefix('.') {
465 Some(remainder) => (true, remainder),
466 None => (false, rest),
467 };
468 Some(fill_number(
469 negative,
470 "",
471 digits,
472 has_decimal,
473 remainder,
474 spec,
475 grouping(spec),
476 ))
477}
478
479fn double_to_string(
482 value: f64,
483 code: char,
484 precision: usize,
485 alternate: bool,
486 add_dot_0: bool,
487 no_neg_0: bool,
488) -> String {
489 let upper = code.is_ascii_uppercase();
490 let code = code.to_ascii_lowercase();
491 let case = |text: String| if upper { text.to_uppercase() } else { text };
492 if value.is_nan() {
493 return case("nan".to_string());
495 }
496 if value.is_infinite() {
497 return case(if value < 0.0 { "-inf" } else { "inf" }.to_string());
498 }
499 let magnitude = value.abs();
502 let (digits, mut decpt) = match code {
503 'e' => dtoa_significant(magnitude, precision + 1),
504 'g' => dtoa_significant(magnitude, precision.max(1)),
505 'f' => dtoa_fixed(magnitude, precision),
506 _ => dtoa_shortest(magnitude),
507 };
508 let precision = match code {
509 'e' => precision + 1,
510 'g' => precision.max(1),
511 _ => precision,
512 } as i64;
513 let digits_len = digits.len() as i64;
514 let mut use_exp = false;
515 let mut vdigits_end = digits_len;
516 match code {
517 'e' => {
518 use_exp = true;
519 vdigits_end = precision;
520 }
521 'f' => vdigits_end = decpt + precision,
522 'g' => {
523 let limit = if add_dot_0 { precision - 1 } else { precision };
524 if decpt <= -4 || decpt > limit {
525 use_exp = true;
526 }
527 if alternate {
528 vdigits_end = precision;
529 }
530 }
531 _ => {
532 if decpt <= -4 || decpt > 16 {
533 use_exp = true;
534 }
535 }
536 }
537 let mut exponent = 0;
538 if use_exp {
539 exponent = decpt - 1;
540 decpt = 1;
541 }
542 let vdigits_start = if decpt <= 0 { decpt - 1 } else { 0 };
543 vdigits_end = if !use_exp && add_dot_0 {
544 vdigits_end.max(decpt + 1)
545 } else {
546 vdigits_end.max(decpt)
547 };
548
549 let zero_value = digits.bytes().all(|b| b == b'0');
550 let negative = value.is_sign_negative() && !(no_neg_0 && zero_value);
551 let zeros = |count: i64| "0".repeat(count.max(0) as usize);
552 let mut out = String::new();
553 if negative {
554 out.push('-');
555 }
556 if decpt <= 0 {
557 out.push_str(&zeros(decpt - vdigits_start));
558 out.push('.');
559 out.push_str(&zeros(-decpt));
560 } else {
561 out.push_str(&zeros(-vdigits_start));
562 }
563 if 0 < decpt && decpt <= digits_len {
564 out.push_str(&digits[..decpt as usize]);
565 out.push('.');
566 out.push_str(&digits[decpt as usize..]);
567 } else {
568 out.push_str(&digits);
569 }
570 if digits_len < decpt {
571 out.push_str(&zeros(decpt - digits_len));
572 out.push('.');
573 out.push_str(&zeros(vdigits_end - decpt));
574 } else {
575 out.push_str(&zeros(vdigits_end - digits_len));
576 }
577 if out.ends_with('.') && !alternate {
578 out.pop();
579 }
580 if use_exp {
581 out.push_str(&format!(
582 "e{}{:02}",
583 exp_sign(exponent as i32),
584 exponent.abs()
585 ));
586 }
587 case(out)
588}
589
590fn split_scientific(text: &str) -> (String, i64) {
593 let (mantissa, exponent) = text.split_once('e').expect("LowerExp has an exponent");
594 let exponent: i64 = exponent.parse().expect("integer exponent");
595 let digits: String = mantissa.chars().filter(char::is_ascii_digit).collect();
596 let digits = digits.trim_end_matches('0');
597 if digits.is_empty() {
598 return ("0".to_string(), 1);
599 }
600 (digits.to_string(), exponent + 1)
601}
602
603fn dtoa_shortest(value: f64) -> (String, i64) {
605 split_scientific(&format!("{value:e}"))
606}
607
608fn dtoa_significant(value: f64, count: usize) -> (String, i64) {
610 split_scientific(&format!("{value:.*e}", count - 1))
611}
612
613fn dtoa_fixed(value: f64, precision: usize) -> (String, i64) {
616 if value == 0.0 {
617 return ("0".to_string(), 1);
618 }
619 let text = format!("{value:.precision$}");
620 let (integer, fraction) = text.split_once('.').unwrap_or((&text, ""));
621 let all = format!("{integer}{fraction}");
622 let leading = all.bytes().take_while(|b| *b == b'0').count();
623 let digits = all[leading..].trim_end_matches('0');
624 if digits.is_empty() {
625 return (String::new(), -(precision as i64));
626 }
627 (digits.to_string(), integer.len() as i64 - leading as i64)
628}
629
630fn fill_number(
633 negative: bool,
634 prefix: &str,
635 digits: &str,
636 has_decimal: bool,
637 remainder: &str,
638 spec: &Spec,
639 grouping: Option<(char, usize)>,
640) -> String {
641 let sign = match (spec.sign, negative) {
642 (_, true) => Some('-'),
643 (Some('+'), false) => Some('+'),
644 (Some(' '), false) => Some(' '),
645 _ => None,
646 };
647 let width = spec.width.map_or(-1, |width| width as i64);
648 let non_digit = i64::from(sign.is_some())
649 + prefix.chars().count() as i64
650 + i64::from(has_decimal)
651 + remainder.chars().count() as i64;
652 let min_width = if spec.fill == '0' && spec.align == '=' {
653 width - non_digit
654 } else {
655 0
656 };
657 let grouped = if digits.is_empty() {
658 String::new()
659 } else {
660 insert_thousands_grouping(digits, min_width, grouping)
661 };
662 let padding = width - (non_digit + grouped.chars().count() as i64);
663 let (mut left, mut middle, mut right) = (0, 0, 0);
664 if padding > 0 {
665 match spec.align {
666 '<' => right = padding,
667 '^' => {
668 left = padding / 2;
669 right = padding - left;
670 }
671 '=' => middle = padding,
672 _ => left = padding,
673 }
674 }
675 let fill = |count: i64| spec.fill.to_string().repeat(count as usize);
676 let mut out = fill(left);
677 out.extend(sign);
678 out.push_str(prefix);
679 out.push_str(&fill(middle));
680 out.push_str(&grouped);
681 if has_decimal {
682 out.push('.');
683 }
684 out.push_str(remainder);
685 out.push_str(&fill(right));
686 out
687}
688
689fn insert_thousands_grouping(
692 digits: &str,
693 min_width: i64,
694 grouping: Option<(char, usize)>,
695) -> String {
696 let chars: Vec<char> = digits.chars().collect();
697 let mut remaining = chars.len() as i64;
698 let mut min_width = min_width;
699 let mut pieces: Vec<String> = Vec::new();
701 let mut use_separator = false;
702 let mut piece = |length: i64, remaining: i64, use_separator: bool| {
703 let zeros = (length - remaining).max(0);
704 let count = remaining.min(length).max(0);
705 let mut text = "0".repeat(zeros as usize);
706 text.extend(&chars[(remaining - count) as usize..remaining as usize]);
707 if use_separator {
708 text.push(grouping.map_or(',', |(separator, _)| separator));
709 }
710 pieces.push(text);
711 count
712 };
713 let mut finished = false;
714 if let Some((_, size)) = grouping {
715 loop {
716 let length = (size as i64).min(remaining.max(min_width).max(1));
717 remaining -= piece(length, remaining, use_separator);
718 use_separator = true;
719 min_width -= length;
720 if remaining <= 0 && min_width <= 0 {
721 finished = true;
722 break;
723 }
724 min_width -= 1;
725 }
726 }
727 if !finished {
728 let length = remaining.max(min_width).max(1);
729 piece(length, remaining, use_separator);
730 }
731 pieces.iter().rev().map(String::as_str).collect()
732}
733
734#[cfg(test)]
735mod tests {
736 use super::*;
737
738 fn fmt(value: impl Into<FormatValue>, spec: &str) -> String {
739 format_value(&value.into(), spec).expect("valid spec")
740 }
741
742 #[test]
743 fn floats_print_like_python_str() {
744 for (value, expected) in [
746 (10.0, "10.0"),
747 (0.1, "0.1"),
748 (1e16, "1e+16"),
749 (1.5e16, "1.5e+16"),
750 (1e15, "1000000000000000.0"),
751 (0.0001, "0.0001"),
752 (0.00001, "1e-05"),
753 (-2.5, "-2.5"),
754 (123456.789, "123456.789"),
755 (f64::INFINITY, "inf"),
756 ] {
757 assert_eq!(float_repr(value), expected, "{value}");
758 }
759 }
760
761 #[test]
762 fn specs_match_python_format() {
763 assert_eq!(fmt(42.0, ">3.0f"), " 42");
765 assert_eq!(fmt(99.95, ">3.0f"), "100");
766 assert_eq!(fmt(2.5, ".0f"), "2");
767 assert_eq!(fmt(3.5, ".0f"), "4");
768 assert_eq!(fmt(0.425, ".1%"), "42.5%");
769 assert_eq!(fmt(1234567.891, ",.2f"), "1,234,567.89");
770 assert_eq!(fmt(1234567i64, ","), "1,234,567");
771 assert_eq!(fmt(42i64, "05d"), "00042");
772 assert_eq!(fmt(-42i64, "05d"), "-0042");
773 assert_eq!(fmt(42i64, "+d"), "+42");
774 assert_eq!(fmt(255i64, "#x"), "0xff");
775 assert_eq!(fmt("ab", "*^6"), "**ab**");
776 assert_eq!(fmt("abcdef", ".3"), "abc");
777 assert_eq!(fmt("ab", "5"), "ab ");
778 assert_eq!(fmt(7i64, "5"), " 7");
779 assert_eq!(fmt(1234.5, ".2"), "1.2e+03");
780 assert_eq!(fmt(10.0, ".3"), "10.0");
781 assert_eq!(fmt(10.0, ".3g"), "10");
782 assert_eq!(fmt(0.000012345, "g"), "1.2345e-05");
783 assert_eq!(fmt(1500.0, "e"), "1.500000e+03");
784 assert_eq!(fmt(true, ""), "True");
785 assert_eq!(fmt(true, "d"), "1");
786 assert_eq!(fmt(FormatValue::None, ""), "None");
787 assert_eq!(fmt(3i64, ".1f"), "3.0");
788 assert_eq!(fmt(12.5, ".2"), "1.2e+01");
790 assert_eq!(fmt(2.5, ".0"), "2e+00");
791 assert_eq!(fmt(1e16, ","), "1e+16");
793 assert_eq!(fmt(f64::INFINITY, "%"), "inf%");
794 assert_eq!(fmt(123.0, "#g"), "123.000");
795 assert_eq!(fmt(123.0, "#.3g"), "123.");
796 assert_eq!(fmt(-0.0001, "z.2f"), "0.00");
797 assert_eq!(fmt(1234.5, "n"), "1234.5");
798 assert_eq!(fmt("h\u{e9}llo", "08"), "h\u{e9}llo000");
799 assert_eq!(fmt(1234i64, "010,"), "00,001,234");
801 assert_eq!(fmt(65i64, "c"), "A");
802 assert_eq!(format_value(&FormatValue::from("ab"), "=5"), None);
803 assert_eq!(format_value(&FormatValue::Int(1234), ",n"), None);
804 }
805
806 #[test]
807 fn templates_expand_fields_and_escapes() {
808 let lookup = |name: &str| match name {
809 "task.completed" => Some(FormatValue::Float(3.0)),
810 "task.fields[name]" => Some(FormatValue::from("disk")),
811 _ => None,
812 };
813 assert_eq!(
814 format("{{x}} {task.fields[name]}: {task.completed:>5.1f}", lookup),
815 "{x} disk: 3.0"
816 );
817 assert_eq!(
819 format("{task.nope} {task.completed:q}", lookup),
820 "{task.nope} {task.completed:q}"
821 );
822 assert_eq!(format("{task.completed!s}", lookup), "3.0");
823 assert_eq!(format("open {", lookup), "open {");
824 }
825}