1use crate::types::{int, slice, string};
30
31pub fn Contains(s: impl AsRef<str>, substr: impl AsRef<str>) -> bool {
32 s.as_ref().contains(substr.as_ref())
33}
34
35pub fn Compare(a: impl AsRef<str>, b: impl AsRef<str>) -> int {
37 use std::cmp::Ordering::*;
38 match a.as_ref().cmp(b.as_ref()) {
39 Less => -1,
40 Equal => 0,
41 Greater => 1,
42 }
43}
44
45pub fn Clone(s: impl AsRef<str>) -> string {
49 s.as_ref().to_string()
50}
51
52pub fn HasPrefix(s: impl AsRef<str>, prefix: impl AsRef<str>) -> bool {
53 s.as_ref().starts_with(prefix.as_ref())
54}
55
56pub fn HasSuffix(s: impl AsRef<str>, suffix: impl AsRef<str>) -> bool {
57 s.as_ref().ends_with(suffix.as_ref())
58}
59
60pub fn Index(s: impl AsRef<str>, substr: impl AsRef<str>) -> int {
62 match s.as_ref().find(substr.as_ref()) {
63 Some(i) => i as int,
64 None => -1,
65 }
66}
67
68pub fn LastIndex(s: impl AsRef<str>, substr: impl AsRef<str>) -> int {
69 match s.as_ref().rfind(substr.as_ref()) {
70 Some(i) => i as int,
71 None => -1,
72 }
73}
74
75pub fn Count(s: impl AsRef<str>, substr: impl AsRef<str>) -> int {
76 let s = s.as_ref();
77 let substr = substr.as_ref();
78 if substr.is_empty() {
79 return (s.chars().count() + 1) as int;
80 }
81 s.matches(substr).count() as int
82}
83
84pub fn Split(s: impl AsRef<str>, sep: impl AsRef<str>) -> slice<string> {
85 let s = s.as_ref();
86 let sep = sep.as_ref();
87 if sep.is_empty() {
88 return s.chars().map(|c| c.to_string()).collect();
89 }
90 s.split(sep).map(String::from).collect()
91}
92
93pub fn SplitN(s: impl AsRef<str>, sep: impl AsRef<str>, n: int) -> slice<string> {
95 if n == 0 {
96 return slice::new();
97 }
98 let s = s.as_ref();
99 let sep = sep.as_ref();
100 if n < 0 {
101 return Split(s, sep);
102 }
103 s.splitn(n as usize, sep).map(String::from).collect()
104}
105
106pub fn Join(elems: &[string], sep: impl AsRef<str>) -> string {
107 elems.join(sep.as_ref())
108}
109
110pub fn Replace(s: impl AsRef<str>, old: impl AsRef<str>, new: impl AsRef<str>, n: int) -> string {
112 let s = s.as_ref();
113 let old = old.as_ref();
114 let new = new.as_ref();
115 if n < 0 {
116 s.replace(old, new)
117 } else {
118 s.replacen(old, new, n as usize)
119 }
120}
121
122pub fn ReplaceAll(s: impl AsRef<str>, old: impl AsRef<str>, new: impl AsRef<str>) -> string {
123 s.as_ref().replace(old.as_ref(), new.as_ref())
124}
125
126pub fn ToUpper(s: impl AsRef<str>) -> string {
127 s.as_ref().to_uppercase()
128}
129
130pub fn ToLower(s: impl AsRef<str>) -> string {
131 s.as_ref().to_lowercase()
132}
133
134pub fn TrimSpace(s: impl AsRef<str>) -> string {
135 s.as_ref().trim().to_string()
136}
137
138pub fn TrimPrefix(s: impl AsRef<str>, prefix: impl AsRef<str>) -> string {
139 let s = s.as_ref();
140 s.strip_prefix(prefix.as_ref()).unwrap_or(s).to_string()
141}
142
143pub fn TrimSuffix(s: impl AsRef<str>, suffix: impl AsRef<str>) -> string {
144 let s = s.as_ref();
145 s.strip_suffix(suffix.as_ref()).unwrap_or(s).to_string()
146}
147
148pub fn Trim(s: impl AsRef<str>, cutset: impl AsRef<str>) -> string {
149 let cutset = cutset.as_ref().to_string();
150 s.as_ref().trim_matches(|c: char| cutset.contains(c)).to_string()
151}
152
153pub fn Fields(s: impl AsRef<str>) -> slice<string> {
154 s.as_ref().split_whitespace().map(String::from).collect()
155}
156
157pub fn Repeat(s: impl AsRef<str>, count: int) -> string {
158 if count < 0 {
159 panic!("strings: negative Repeat count");
160 }
161 s.as_ref().repeat(count as usize)
162}
163
164pub fn EqualFold(s: impl AsRef<str>, t: impl AsRef<str>) -> bool {
169 let mut si = s.as_ref().chars();
170 let mut ti = t.as_ref().chars();
171 loop {
172 let a = si.next();
173 let b = ti.next();
174 match (a, b) {
175 (None, None) => return true,
176 (None, _) | (_, None) => return false,
177 (Some(a), Some(b)) => {
178 if a == b { continue; }
179 let a_low: Vec<char> = a.to_lowercase().collect();
180 let b_low: Vec<char> = b.to_lowercase().collect();
181 if a_low != b_low { return false; }
182 }
183 }
184 }
185}
186
187#[derive(Debug, Clone, Default)]
200pub struct Builder {
201 inner: string,
202}
203
204impl Builder {
205 pub fn new() -> Self { Builder::default() }
206
207 pub fn WriteString(&mut self, s: impl AsRef<str>) -> (int, crate::errors::error) {
208 let s = s.as_ref();
209 self.inner.push_str(s);
210 (s.len() as int, crate::errors::nil)
211 }
212
213 pub fn WriteByte(&mut self, b: crate::types::byte) -> crate::errors::error {
214 if b < 0x80 {
218 self.inner.push(b as char);
219 crate::errors::nil
220 } else {
221 crate::errors::New("strings.Builder: non-ASCII byte; use WriteRune")
222 }
223 }
224
225 pub fn WriteRune(&mut self, r: char) -> (int, crate::errors::error) {
226 let n = r.len_utf8();
227 self.inner.push(r);
228 (n as int, crate::errors::nil)
229 }
230
231 pub fn String(&self) -> string {
232 self.inner.clone()
233 }
234
235 #[allow(non_snake_case)]
238 pub fn Cap(&self) -> int { self.inner.capacity() as int }
239
240 #[allow(non_snake_case)]
243 pub fn Write(&mut self, p: &[crate::types::byte]) -> (int, crate::errors::error) {
244 match std::str::from_utf8(p) {
245 Ok(s) => { self.inner.push_str(s); (p.len() as int, crate::errors::nil) },
246 Err(_) => (0, crate::errors::New("strings.Builder.Write: invalid UTF-8")),
247 }
248 }
249
250 pub fn Len(&self) -> int {
251 self.inner.len() as int
252 }
253
254 pub fn Reset(&mut self) {
255 self.inner.clear();
256 }
257
258 pub fn Grow(&mut self, n: int) {
259 if n > 0 {
260 self.inner.reserve(n as usize);
261 }
262 }
263
264 pub fn len(&self) -> usize {
266 self.inner.len()
267 }
268}
269
270impl std::fmt::Display for Builder {
271 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
272 write!(f, "{}", self.inner)
273 }
274}
275
276#[derive(Debug, Clone)]
284pub struct Reader {
285 data: String,
286 pos: usize,
287}
288
289impl Reader {
290 pub fn Len(&self) -> int {
291 (self.data.len().saturating_sub(self.pos)) as int
292 }
293
294 pub fn Size(&self) -> crate::types::int64 {
295 self.data.len() as crate::types::int64
296 }
297
298 pub fn Read(&mut self, p: &mut [crate::types::byte]) -> (int, crate::errors::error) {
299 if self.pos >= self.data.len() {
300 return (0, crate::io::EOF());
301 }
302 let bytes = self.data.as_bytes();
303 let n = (bytes.len() - self.pos).min(p.len());
304 p[..n].copy_from_slice(&bytes[self.pos..self.pos + n]);
305 self.pos += n;
306 (n as int, crate::errors::nil)
307 }
308
309 pub fn ReadByte(&mut self) -> (crate::types::byte, crate::errors::error) {
310 let bytes = self.data.as_bytes();
311 if self.pos >= bytes.len() {
312 return (0, crate::io::EOF());
313 }
314 let b = bytes[self.pos];
315 self.pos += 1;
316 (b, crate::errors::nil)
317 }
318
319 pub fn UnreadByte(&mut self) -> crate::errors::error {
320 if self.pos == 0 {
321 return crate::errors::New("strings.Reader.UnreadByte: at beginning of string");
322 }
323 self.pos -= 1;
324 crate::errors::nil
325 }
326
327 #[allow(non_snake_case)]
330 pub fn ReadAt(&self, p: &mut [crate::types::byte], off: crate::types::int64) -> (int, crate::errors::error) {
331 if off < 0 {
332 return (0, crate::errors::New("strings.Reader.ReadAt: negative offset"));
333 }
334 let off = off as usize;
335 if off >= self.data.len() {
336 return (0, crate::io::EOF());
337 }
338 let bytes = self.data.as_bytes();
339 let available = bytes.len() - off;
340 let n = available.min(p.len());
341 p[..n].copy_from_slice(&bytes[off..off + n]);
342 if n < p.len() {
343 (n as int, crate::io::EOF())
344 } else {
345 (n as int, crate::errors::nil)
346 }
347 }
348
349 pub fn Seek(&mut self, offset: crate::types::int64, whence: int) -> (crate::types::int64, crate::errors::error) {
350 let new_pos: i64 = match whence {
351 0 => offset,
352 1 => self.pos as i64 + offset,
353 2 => self.data.len() as i64 + offset,
354 _ => return (0, crate::errors::New("strings.Reader.Seek: invalid whence")),
355 };
356 if new_pos < 0 {
357 return (0, crate::errors::New("strings.Reader.Seek: negative position"));
358 }
359 self.pos = new_pos as usize;
360 (new_pos, crate::errors::nil)
361 }
362}
363
364impl std::io::Read for Reader {
365 fn read(&mut self, buf: &mut [u8]) -> std::io::Result<usize> {
366 let bytes = self.data.as_bytes();
367 if self.pos >= bytes.len() {
368 return Ok(0);
369 }
370 let n = (bytes.len() - self.pos).min(buf.len());
371 buf[..n].copy_from_slice(&bytes[self.pos..self.pos + n]);
372 self.pos += n;
373 Ok(n)
374 }
375}
376
377#[allow(non_snake_case)]
379pub fn NewReader(s: impl Into<String>) -> Reader {
380 Reader { data: s.into(), pos: 0 }
381}
382
383#[derive(Debug, Clone)]
392pub struct Replacer {
393 pairs: Vec<(String, String)>,
394}
395
396impl Replacer {
397 pub fn Replace(&self, s: impl AsRef<str>) -> string {
398 let s = s.as_ref();
399 let mut out = String::with_capacity(s.len());
400 let bytes = s.as_bytes();
401 let mut i = 0usize;
402 'outer: while i < bytes.len() {
403 for (old, new) in &self.pairs {
404 if old.is_empty() { continue; }
405 let ob = old.as_bytes();
406 if i + ob.len() <= bytes.len() && &bytes[i..i + ob.len()] == ob {
407 out.push_str(new);
408 i += ob.len();
409 continue 'outer;
410 }
411 }
412 let ch = s[i..].chars().next().unwrap();
414 out.push(ch);
415 i += ch.len_utf8();
416 }
417 out
418 }
419
420 pub fn WriteString<W: std::io::Write>(&self, w: &mut W, s: impl AsRef<str>) -> (int, crate::errors::error) {
421 let result = self.Replace(s);
422 match w.write(result.as_bytes()) {
423 Ok(n) => (n as int, crate::errors::nil),
424 Err(e) => (0, crate::errors::New(&e.to_string())),
425 }
426 }
427}
428
429#[allow(non_snake_case)]
433pub fn NewReplacer(pairs: &[impl AsRef<str>]) -> Replacer {
434 if pairs.len() % 2 != 0 {
435 panic!("strings.NewReplacer: odd argument count");
436 }
437 let pairs = pairs.chunks(2)
438 .map(|c| (c[0].as_ref().to_string(), c[1].as_ref().to_string()))
439 .collect();
440 Replacer { pairs }
441}
442
443#[allow(non_snake_case)]
453pub fn Map(mut f: impl FnMut(char) -> char, s: impl AsRef<str>) -> string {
454 let mut out = String::with_capacity(s.as_ref().len());
455 for c in s.as_ref().chars() {
456 let r = f(c);
457 if r != '\0' {
458 out.push(r);
459 }
460 }
461 out
462}
463
464#[allow(non_snake_case)]
467pub fn ContainsAny(s: impl AsRef<str>, chars: impl AsRef<str>) -> bool {
468 let set: Vec<char> = chars.as_ref().chars().collect();
469 s.as_ref().chars().any(|c| set.contains(&c))
470}
471
472#[allow(non_snake_case)]
473pub fn ContainsRune(s: impl AsRef<str>, r: char) -> bool {
474 s.as_ref().contains(r)
475}
476
477#[allow(non_snake_case)]
478pub fn IndexAny(s: impl AsRef<str>, chars: impl AsRef<str>) -> int {
479 let s = s.as_ref();
480 let set: Vec<char> = chars.as_ref().chars().collect();
481 for (i, c) in s.char_indices() {
482 if set.contains(&c) {
483 return i as int;
484 }
485 }
486 -1
487}
488
489#[allow(non_snake_case)]
490pub fn IndexByte(s: impl AsRef<str>, b: crate::types::byte) -> int {
491 s.as_ref().as_bytes().iter().position(|x| *x == b).map(|i| i as int).unwrap_or(-1)
492}
493
494#[allow(non_snake_case)]
495pub fn IndexRune(s: impl AsRef<str>, r: char) -> int {
496 let s = s.as_ref();
497 s.find(r).map(|i| i as int).unwrap_or(-1)
498}
499
500#[allow(non_snake_case)]
503pub fn Cut(s: impl AsRef<str>, sep: impl AsRef<str>) -> (string, string, bool) {
504 let s = s.as_ref(); let sep = sep.as_ref();
505 match s.find(sep) {
506 Some(i) => (s[..i].to_string(), s[i + sep.len()..].to_string(), true),
507 None => (s.to_string(), String::new(), false),
508 }
509}
510
511#[allow(non_snake_case)]
513pub fn CutPrefix(s: impl AsRef<str>, prefix: impl AsRef<str>) -> (string, bool) {
514 let s = s.as_ref(); let p = prefix.as_ref();
515 match s.strip_prefix(p) {
516 Some(rest) => (rest.to_string(), true),
517 None => (s.to_string(), false),
518 }
519}
520
521#[allow(non_snake_case)]
523pub fn CutSuffix(s: impl AsRef<str>, suffix: impl AsRef<str>) -> (string, bool) {
524 let s = s.as_ref(); let suf = suffix.as_ref();
525 match s.strip_suffix(suf) {
526 Some(rest) => (rest.to_string(), true),
527 None => (s.to_string(), false),
528 }
529}
530
531#[allow(non_snake_case)]
533pub fn TrimLeft(s: impl AsRef<str>, cutset: impl AsRef<str>) -> string {
534 let cut: Vec<char> = cutset.as_ref().chars().collect();
535 s.as_ref().trim_start_matches(|c: char| cut.contains(&c)).to_string()
536}
537
538#[allow(non_snake_case)]
540pub fn TrimRight(s: impl AsRef<str>, cutset: impl AsRef<str>) -> string {
541 let cut: Vec<char> = cutset.as_ref().chars().collect();
542 s.as_ref().trim_end_matches(|c: char| cut.contains(&c)).to_string()
543}
544
545#[allow(non_snake_case)]
547pub fn LastIndexByte(s: impl AsRef<str>, c: crate::types::byte) -> int {
548 s.as_ref().as_bytes().iter().rposition(|&x| x == c).map(|i| i as int).unwrap_or(-1)
549}
550
551#[allow(non_snake_case)]
554pub fn LastIndexAny(s: impl AsRef<str>, chars: impl AsRef<str>) -> int {
555 let s = s.as_ref(); let chars = chars.as_ref();
556 let set: Vec<char> = chars.chars().collect();
557 let mut best: i64 = -1;
558 for (i, c) in s.char_indices() {
559 if set.contains(&c) { best = i as i64; }
560 }
561 best
562}
563
564#[allow(non_snake_case)]
566pub fn IndexFunc(s: impl AsRef<str>, mut f: impl FnMut(char) -> bool) -> int {
567 for (i, c) in s.as_ref().char_indices() {
568 if f(c) { return i as int; }
569 }
570 -1
571}
572
573#[allow(non_snake_case)]
575pub fn LastIndexFunc(s: impl AsRef<str>, mut f: impl FnMut(char) -> bool) -> int {
576 let mut best: i64 = -1;
577 for (i, c) in s.as_ref().char_indices() {
578 if f(c) { best = i as i64; }
579 }
580 best
581}
582
583#[allow(non_snake_case)]
585pub fn TrimFunc(s: impl AsRef<str>, mut f: impl FnMut(char) -> bool) -> string {
586 s.as_ref().trim_matches(|c: char| f(c)).to_string()
587}
588
589#[allow(non_snake_case)]
591pub fn TrimLeftFunc(s: impl AsRef<str>, mut f: impl FnMut(char) -> bool) -> string {
592 s.as_ref().trim_start_matches(|c: char| f(c)).to_string()
593}
594
595#[allow(non_snake_case)]
597pub fn TrimRightFunc(s: impl AsRef<str>, mut f: impl FnMut(char) -> bool) -> string {
598 s.as_ref().trim_end_matches(|c: char| f(c)).to_string()
599}
600
601#[allow(non_snake_case)]
604pub fn SplitAfter(s: impl AsRef<str>, sep: impl AsRef<str>) -> slice<string> {
605 let s = s.as_ref(); let sep = sep.as_ref();
606 if sep.is_empty() {
607 return s.chars().map(|c| c.to_string()).collect();
609 }
610 let mut out = Vec::new();
611 let mut start = 0usize;
612 loop {
613 match s[start..].find(sep) {
614 Some(i) => {
615 let end = start + i + sep.len();
616 out.push(s[start..end].to_string());
617 start = end;
618 }
619 None => {
620 out.push(s[start..].to_string());
621 break;
622 }
623 }
624 }
625 out
626}
627
628#[allow(non_snake_case)]
630pub fn FieldsFunc(s: impl AsRef<str>, mut f: impl FnMut(char) -> bool) -> slice<string> {
631 s.as_ref()
632 .split(|c: char| f(c))
633 .filter(|seg| !seg.is_empty())
634 .map(|seg| seg.to_string())
635 .collect()
636}
637
638#[allow(non_snake_case)]
639pub fn Title(s: impl AsRef<str>) -> string {
640 let s = s.as_ref();
642 let mut out = String::with_capacity(s.len());
643 let mut at_word_boundary = true;
644 for c in s.chars() {
645 if c.is_whitespace() {
646 at_word_boundary = true;
647 out.push(c);
648 } else if at_word_boundary {
649 for uc in c.to_uppercase() {
650 out.push(uc);
651 }
652 at_word_boundary = false;
653 } else {
654 out.push(c);
655 }
656 }
657 out
658}
659
660#[cfg(test)]
661mod tests {
662 use super::*;
663
664 #[test]
665 fn contains_and_prefix() {
666 assert!(Contains("hello world", "world"));
667 assert!(!Contains("hello", "xyz"));
668 assert!(HasPrefix("foobar", "foo"));
669 assert!(HasSuffix("foobar", "bar"));
670 }
671
672 #[test]
673 fn index_returns_minus_one_when_absent() {
674 assert_eq!(Index("hello", "ll"), 2);
675 assert_eq!(Index("hello", "z"), -1);
676 assert_eq!(LastIndex("banana", "an"), 3);
677 }
678
679 #[test]
680 fn count_substr_and_empty() {
681 assert_eq!(Count("banana", "a"), 3);
682 assert_eq!(Count("xx", ""), 3); }
684
685 #[test]
686 fn split_and_join() {
687 let v = Split("a,b,c", ",");
688 assert_eq!(v, vec!["a", "b", "c"]);
689 assert_eq!(Join(&v, "-"), "a-b-c");
690 }
691
692 #[test]
693 fn split_n_caps_results() {
694 let v = SplitN("a,b,c,d", ",", 2);
695 assert_eq!(v, vec!["a", "b,c,d"]);
696 let v = SplitN("a,b,c", ",", -1);
697 assert_eq!(v.len(), 3);
698 let v = SplitN("a,b,c", ",", 0);
699 assert!(v.is_empty());
700 }
701
702 #[test]
703 fn replace_and_replace_all() {
704 assert_eq!(Replace("aaa", "a", "b", 2), "bba");
705 assert_eq!(ReplaceAll("aaa", "a", "b"), "bbb");
706 }
707
708 #[test]
709 fn case_change() {
710 assert_eq!(ToUpper("hello"), "HELLO");
711 assert_eq!(ToLower("HELLO"), "hello");
712 }
713
714 #[test]
715 fn trim_variants() {
716 assert_eq!(TrimSpace(" hi "), "hi");
717 assert_eq!(TrimPrefix("foobar", "foo"), "bar");
718 assert_eq!(TrimSuffix("foobar", "bar"), "foo");
719 assert_eq!(Trim("---abc--", "-"), "abc");
720 }
721
722 #[test]
723 fn fields_splits_on_whitespace() {
724 assert_eq!(Fields(" a b\tc\n"), vec!["a", "b", "c"]);
725 }
726
727 #[test]
728 fn repeat_and_equalfold() {
729 assert_eq!(Repeat("ab", 3), "ababab");
730 assert!(EqualFold("HELLO", "hello"));
731 assert!(!EqualFold("hello", "world"));
732 }
733
734 #[test]
735 fn builder_writes_and_resets() {
736 let mut b = Builder::new();
737 b.WriteString("hello ");
738 b.WriteString("world");
739 b.WriteByte(b'!');
740 b.WriteRune('λ');
741 assert_eq!(b.String(), "hello world!λ");
742 assert_eq!(b.Len(), "hello world!λ".len() as int);
743 b.Reset();
744 assert_eq!(b.Len(), 0);
745 }
746
747 #[test]
748 fn builder_writerune_returns_bytes_written() {
749 let mut b = Builder::new();
750 let (n, _) = b.WriteRune('a');
751 assert_eq!(n, 1);
752 let (n, _) = b.WriteRune('λ');
753 assert_eq!(n, 2);
754 let (n, _) = b.WriteRune('漢');
755 assert_eq!(n, 3);
756 }
757
758 #[test]
759 fn reader_reads_bytes() {
760 let mut r = NewReader("hello");
761 let mut buf = [0u8; 3];
762 let (n, _) = r.Read(&mut buf);
763 assert_eq!(n, 3);
764 assert_eq!(&buf, b"hel");
765 let (n, _) = r.Read(&mut buf);
766 assert_eq!(n, 2);
767 assert_eq!(&buf[..2], b"lo");
768 }
769
770 #[test]
771 fn reader_seek() {
772 let mut r = NewReader("abcdef");
773 r.Seek(2, 0);
774 let (b, _) = r.ReadByte();
775 assert_eq!(b, b'c');
776 r.Seek(-1, 2);
777 let (b, _) = r.ReadByte();
778 assert_eq!(b, b'f');
779 }
780
781 #[test]
782 fn replacer_replaces_multiple() {
783 let r = NewReplacer(&["a", "1", "b", "2", "c", "3"]);
784 assert_eq!(r.Replace("abc cab"), "123 312");
785 }
786
787 #[test]
788 fn replacer_leaves_unmatched() {
789 let r = NewReplacer(&["foo", "FOO"]);
790 assert_eq!(r.Replace("foo bar baz"), "FOO bar baz");
791 }
792
793 #[test]
794 fn map_transforms_chars() {
795 let shout = Map(|c| c.to_ascii_uppercase(), "hello");
796 assert_eq!(shout, "HELLO");
797 let drop_vowels = Map(|c| if "aeiouAEIOU".contains(c) { '\0' } else { c }, "HELLO");
798 assert_eq!(drop_vowels, "HLL");
799 }
800
801 #[test]
802 fn contains_any_and_index_any() {
803 assert!(ContainsAny("hello", "xyz!o"));
804 assert!(!ContainsAny("hello", "xyz"));
805 assert_eq!(IndexAny("hello", "lo"), 2);
806 assert_eq!(IndexAny("abc", "xyz"), -1);
807 assert_eq!(IndexRune("héllo", 'é'), 1);
808 assert_eq!(IndexByte("hello", b'l'), 2);
809 }
810
811 #[test]
812 fn title_upcases_words() {
813 assert_eq!(Title("hello world"), "Hello World");
814 }
815}