1use proc_macro::{
33 Delimiter, Group, Literal, Punct, Spacing, Span, TokenStream, TokenTree,
34};
35use quote::quote;
36
37#[proc_macro_attribute]
132pub fn resext(attr: TokenStream, item: TokenStream) -> TokenStream {
133 let mut errs = vec![];
134 let input = match EnumInput::from_tokens(item, &mut errs) {
135 Some(i) => i,
136 None => return errs.into_iter().collect(),
137 };
138
139 let original: &proc_macro2::TokenStream = &input.original.into();
140
141 let args = parse_args(attr, &mut errs).unwrap_or_default();
142
143 let manual_name = &input.name;
144 let enum_name = proc_macro2::Ident::new(
145 manual_name.to_string().as_str(),
146 manual_name.span().into(),
147 );
148 let vis: &proc_macro2::TokenStream = &input.vis.into();
149
150 let alias = args.alias.unwrap_or_else(|| String::from("Res"));
151 let struct_name = quote::format_ident!("{}Err", alias);
152 let buf_name = quote::format_ident!("{}Buf", alias);
153 let trait_name = quote::format_ident!("{}Ext", alias);
154 let alias = quote::format_ident!("{}", alias);
155
156 let alloc = args.alloc;
157
158 let variants = &input.variants;
159
160 let include_variant = args.include_variant;
161 let display_match_arms = variants.iter().map(|variant| {
162 let variant_name = proc_macro2::Ident::new(variant.name.to_string().as_str(), variant.name.span().into());
163
164 match &variant.kind {
165 VariantKind::Unnamed(_) => {
166 if include_variant {
167 quote! {
168 #enum_name::#variant_name(var) => write!(f, "{}: {}", stringify!(#variant_name), var),
169 }
170 } else {
171 quote! {
172 #enum_name::#variant_name(var) => write!(f, "{}", var),
173 }
174 }
175 }
176
177 VariantKind::Named(variant_field, _) => {
178 let variant_field = proc_macro2::Ident::new(variant_field.to_string().as_str(), variant_field.span().into());
179
180 if include_variant {
181 quote! {
182 #enum_name::#variant_name { #variant_field } => write!(f, "{}: {}: {}", stringify!(#variant_name), stringify!(#variant_field), #variant_field),
183 }
184 } else {
185 quote! {
186 #enum_name::#variant_name { #variant_field } => write!(f, "{}", #variant_field),
187 }
188 }
189 }
190
191 VariantKind::Unit => {
192 quote! {
193 #enum_name::#variant_name => write!(f, "{}", stringify!(#variant_name)),
194 }
195 }
196 }
197 });
198
199 let from_impls = variants.iter().filter_map(|variant| {
200 let variant_name = proc_macro2::Ident::new(variant.name.to_string().as_str(), variant.name.span().into());
201
202 match &variant.kind {
203 VariantKind::Unnamed(field_type) => {
204 let field_type: proc_macro2::TokenStream = field_type.clone().into();
205 Some(quote! {
206 impl From<#field_type> for #enum_name {
207 fn from(value: #field_type) -> Self {
208 Self::#variant_name(value)
209 }
210 }
211
212 impl From<#field_type> for #struct_name {
213 fn from(value: #field_type) -> Self {
214 Self { msg: #buf_name::new(), source: #enum_name::#variant_name(value) }
215 }
216 }
217 })
218 }
219
220 VariantKind::Named(field_name, field_type) => {
221 let field_type: proc_macro2::TokenStream = field_type.clone().into();
222 let field_name = proc_macro2::Ident::new(field_name.to_string().as_str(), field_name.span().into());
223 Some(quote! {
224 impl From<#field_type> for #enum_name {
225 fn from(value: #field_type) -> Self {
226 Self::#variant_name { #field_name: value }
227 }
228 }
229
230 impl From<#field_type> for #struct_name {
231 fn from(value: #field_type) -> Self {
232 Self { msg: #buf_name::new(), source: #enum_name::#variant_name { #field_name: value } }
233 }
234 }
235 })
236 }
237
238 _ => None,
239 }
240 });
241
242 let prefix = args.prefix.unwrap_or_default();
243 let suffix = args.suffix.unwrap_or_default();
244 let msg_prefix = args.msg_prefix.unwrap_or_default();
245 let msg_suffix = args.msg_suffix.unwrap_or_default();
246 let delimiter = args.delimiter.unwrap_or_else(|| String::from("\n - "));
247 let source_prefix =
248 args.source_prefix.unwrap_or_else(|| String::from("Error: "));
249 let buf_size = args.buf_size.unwrap_or(64);
250
251 let gen_buf = {
252 if !alloc {
253 quote! {
254 struct #buf_name {
255 curr_pos: u16,
256 buf: [u8; #buf_size],
257 truncate: bool,
258 }
259
260 impl #buf_name {
261 fn new() -> Self {
262 Self { buf: [0; #buf_size], curr_pos: 0, truncate: false }
263 }
264
265 fn get_slice(&self) -> &[u8] {
266 &self.buf[..self.curr_pos as usize]
267 }
268
269 fn is_empty(&self) -> bool {
270 self.curr_pos == 0
271 }
272
273 fn truncate(&self) -> bool {
274 self.truncate
275 }
276 }
277
278 impl core::fmt::Write for #buf_name {
279 fn write_str(&mut self, s: &str) -> core::fmt::Result {
280 let bytes = s.as_bytes();
281 let pos = self.curr_pos as usize;
282 let cap = #buf_size - pos;
283
284 let limit = if cap < bytes.len() {
285 self.truncate = true;
286 cap
287 } else {
288 bytes.len()
289 };
290
291 let to_copy = match bytes[..limit]
292 .iter()
293 .rposition(|&b| (b & 0xC0) != 0x80)
294 {
295 Some(start_of_last_char) => {
296 let last_char_byte = bytes[start_of_last_char];
297 let width = match last_char_byte {
298 0..=127 => 1,
299 192..=223 => 2,
300 224..=239 => 3,
301 240..=247 => 4,
302 _ => 1,
303 };
304 if start_of_last_char + width <= limit {
305 start_of_last_char + width
306 } else {
307 start_of_last_char
308 }
309 }
310 None => 0,
311 };
312
313 self.buf[pos..pos + to_copy].copy_from_slice(&bytes[..to_copy]);
314 self.curr_pos += to_copy as u16;
315
316 Ok(())
317 }
318 }
319 }
320 } else {
321 quote! {
322 mod __private_alloc {
323 extern crate alloc;
324 pub(crate) use alloc::vec::Vec;
325 }
326
327 enum #buf_name {
328 Stack { buf: [u8; #buf_size], curr_pos: u16 },
329 Heap(__private_alloc::Vec<u8>),
330 }
331
332 impl #buf_name {
333 fn new() -> Self {
334 Self::Stack { buf: [0; #buf_size], curr_pos: 0 }
335 }
336
337 fn get_slice(&self) -> &[u8] {
338 match self {
339 Self::Stack { buf, curr_pos } => &buf[..*curr_pos as usize],
340 Self::Heap(buf) => buf,
341 }
342 }
343
344 fn truncate(&self) -> bool {
345 false
346 }
347
348 fn is_empty(&self) -> bool {
349 match self {
350 Self::Heap(buf) => buf.is_empty(),
351 Self::Stack { buf: _, curr_pos } => *curr_pos == 0,
352 }
353 }
354 }
355
356 impl core::fmt::Write for #buf_name {
357 fn write_str(&mut self, s: &str) -> core::fmt::Result {
358 match self {
359 Self::Heap(buf) => buf.extend_from_slice(s.as_bytes()),
360 Self::Stack { buf, curr_pos } => {
361 let bytes = s.as_bytes();
362 let pos = *curr_pos as usize;
363 let cap = #buf_size - pos;
364
365 if bytes.len() > cap {
366 {
367 extern crate alloc;
368 let mut vec = alloc::vec::Vec::new();
369 vec.reserve_exact(pos + bytes.len());
370
371 vec.extend_from_slice(&buf[..pos]);
372 vec.extend_from_slice(bytes);
373
374 *self = #buf_name::Heap(vec);
375 }
376 } else {
377 buf[pos..pos + bytes.len()].copy_from_slice(bytes);
378 *curr_pos += bytes.len() as u16;
379 }
380 }
381 }
382
383 Ok(())
384 }
385 }
386 }
387 }
388 };
389
390 let expanded = quote! {
391 #[derive(Debug)]
392 #original
393
394 impl core::fmt::Display for #enum_name {
395 fn fmt(&self, f: &mut core::fmt::Formatter) -> core::fmt::Result {
396 match self {
397 #(#display_match_arms)*
398 }
399 }
400 }
401
402 #[doc(hidden)]
407 #vis struct #struct_name {
408 msg: #buf_name,
409 #vis source: #enum_name
410 }
411 impl core::error::Error for #struct_name {}
412
413
414 impl core::fmt::Write for #struct_name {
415 fn write_str(&mut self, s: &str) -> core::fmt::Result {
416 if s.is_empty() {
417 Ok(())
418 } else {
419 self.msg.write_str(s)
420 }
421 }
422 }
423
424 impl core::fmt::Display for #struct_name {
425 fn fmt(&self, f: &mut core::fmt::Formatter) -> core::fmt::Result {
426 if self.msg.is_empty() {
427 write!(f, "{}{}{}", #source_prefix, &self.source, #suffix)
428 } else {
429 write!(
430 f,
431 "{}{}{}\n{}{}{}",
432 #prefix,
433 unsafe { core::str::from_utf8_unchecked(&self.msg.get_slice()) },
434 if self.msg.truncate() { "..." } else { "" },
435 #source_prefix,
436 self.source,
437 #suffix,
438 )
439 }
440 }
441 }
442
443 impl core::fmt::Debug for #struct_name {
444 fn fmt(&self, f: &mut core::fmt::Formatter) -> core::fmt::Result {
445 if self.msg.is_empty() {
446 write!(f, "{}{:?}{}", #source_prefix, &self.source, #suffix)
447 } else {
448 write!(
449 f,
450 "{}{}{}\n{}{:?}{}",
451 #prefix,
452 unsafe { core::str::from_utf8_unchecked(&self.msg.get_slice()) },
453 if self.msg.truncate() { "..." } else { "" },
454 #source_prefix,
455 self.source,
456 #suffix,
457 )
458 }
459 }
460 }
461
462 impl #struct_name {
463 #[doc(hidden)]
472 #vis fn new<E>(msg: &str, source: E) -> Self where #enum_name: From<E> {
473 use core::fmt::Write;
474 let mut buf = #buf_name::new();
475 let _ = buf.write_str(msg);
476 Self { msg: buf, source: #enum_name::from(source) }
477 }
478
479 #[doc(hidden)]
491 #vis fn from_args<E, F: FnOnce(#struct_name, &str, &str, &str) -> #struct_name>(msg: F, source: E) -> Self where #enum_name: From<E> {
492 use core::fmt::Write;
493
494 let err = Self { msg: #buf_name::new(), source: #enum_name::from(source) };
495
496 msg(err, "", "", "")
497 }
498 }
499
500 impl From<#enum_name> for #struct_name {
501 fn from(value: #enum_name) -> Self {
502 Self { msg: #buf_name::new(), source: value }
503 }
504 }
505
506 #(#from_impls)*
507
508 #[doc(hidden)]
520 #vis trait #trait_name<T, S> {
521 #[doc(hidden)]
533 fn context(self, msg: S) -> Result<T, #struct_name>;
534 }
535
536 impl<T> #trait_name<T, &str> for Result<T, #struct_name> {
537 fn context(self, msg: &str) -> Result<T, #struct_name> {
538 match self {
539 Ok(ok) => Ok(ok),
540 Err(mut err) => {
541 use core::fmt::Write;
542
543 if err.msg.is_empty() {
544 let _ = err.write_str(msg);
545 } else {
546 let _ = err.write_str(#delimiter);
547 let _ = err.write_str(#msg_prefix);
548 let _ = err.write_str(msg);
549 let _ = err.write_str(#msg_suffix);
550 }
551
552 Err(err)
553 }
554 }
555 }
556 }
557
558 impl<T, E> #trait_name<T, &str> for Result<T, E> where #enum_name: From<E> {
559 fn context(self, msg: &str) -> Result<T, #struct_name> {
560 match self {
561 Ok(ok) => Ok(ok),
562 Err(err) => Err(#struct_name::new(msg, err)),
563 }
564 }
565 }
566
567 impl<'a, T, F: FnOnce(#struct_name, &'a str, &'a str, &'a str) -> #struct_name> #trait_name<T, F> for Result<T, #struct_name> {
568 fn context(self, msg: F) -> Result<T, #struct_name> {
569 match self {
570 Ok(ok) => Ok(ok),
571 Err(mut err) => {
572 use core::fmt::Write;
573
574 let err = if err.msg.is_empty() {
575 msg(err, "", "", "")
576 } else {
577 msg(err, #delimiter, #msg_prefix, #msg_suffix)
578 };
579
580 Err(err)
581 }
582 }
583 }
584 }
585
586 impl<'a, T, F: FnOnce(#struct_name, &'a str, &'a str, &'a str) -> #struct_name, E> #trait_name<T, F> for Result<T, E> where #enum_name: From<E> {
587 fn context(self, msg: F) -> Result<T, #struct_name> {
588 match self {
589 Ok(ok) => Ok(ok),
590 Err(err) => {
591 use core::fmt::Write;
592
593 let buf = #buf_name::new();
594 let mut err = #struct_name { msg: buf, source: #enum_name::from(err) };
595
596 let err = msg(err, "", "", "");
597
598 Err(err)
599 }
600 }
601 }
602 }
603
604 #vis type #alias<T> = Result<T, #struct_name>;
605
606 #gen_buf
607 };
608
609 if !errs.is_empty() {
610 errs.into_iter().collect()
611 } else {
612 TokenStream::from(expanded)
613 }
614}
615
616struct EnumInput {
617 vis: TokenStream,
618 name: proc_macro::Ident,
619 variants: Vec<Variant>,
620 original: TokenStream,
621}
622
623struct Variant {
624 name: proc_macro::Ident,
625 kind: VariantKind,
626}
627
628enum VariantKind {
629 Unnamed(TokenStream),
630 Named(proc_macro::Ident, TokenStream),
631 Unit,
632}
633
634impl EnumInput {
635 fn from_tokens(
636 tokens: TokenStream,
637 errs: &mut Vec<TokenStream>,
638 ) -> Option<Self> {
639 let original = tokens.clone();
640 let mut iter = tokens.into_iter().peekable();
641
642 let mut vis = TokenStream::new();
643 loop {
644 match iter.peek() {
645 Some(TokenTree::Ident(i))
646 if i.to_string().as_str() == "enum" =>
647 {
648 break;
649 }
650 Some(_) => vis.extend(iter.next()),
651 None => {
652 errs.push(construct_err(
653 "expected enum item",
654 Span::call_site(),
655 ));
656 break;
657 }
658 }
659 }
660
661 match iter.next() {
662 Some(TokenTree::Ident(i)) if i.to_string().as_str() == "enum" => {}
663 Some(tt) => errs.push(construct_err(
664 &format!("expected enum keyword, found: {}", tt),
665 tt.span(),
666 )),
667 None => errs.push(construct_err(
668 "expected enum keyword",
669 Span::call_site(),
670 )),
671 }
672
673 let mut name = proc_macro::Ident::new("Var", Span::call_site());
674 match iter.next() {
675 Some(TokenTree::Ident(i)) => name = i,
676 Some(tt) => errs.push(construct_err(
677 &format!("expected enum identifier, found: {}", tt),
678 tt.span(),
679 )),
680 None => errs.push(construct_err(
681 "expected enum item name",
682 Span::call_site(),
683 )),
684 }
685
686 let body = match iter.next() {
687 Some(TokenTree::Group(g)) if g.delimiter() == Delimiter::Brace => {
688 Some(g)
689 }
690 Some(tt) => {
691 errs.push(construct_err("expected enum body", tt.span()));
692 None
693 }
694 None => {
695 errs.push(construct_err(
696 "expected enum body",
697 Span::call_site(),
698 ));
699 None
700 }
701 };
702
703 let variants = Self::parse_variants(body, errs);
704
705 if errs.is_empty() {
706 Some(EnumInput { vis, name, variants, original })
707 } else {
708 None
709 }
710 }
711
712 fn parse_variants(
713 group: Option<Group>,
714 errs: &mut Vec<TokenStream>,
715 ) -> Vec<Variant> {
716 if group.is_none() {
717 return vec![];
718 }
719
720 let mut iter =
721 unsafe { group.unwrap_unchecked() }.stream().into_iter().peekable();
722 let mut out = vec![];
723
724 let mut first = true;
725
726 while let Some(token) = iter.next() {
727 let token = if first {
728 first = false;
729 token
730 } else {
731 match token {
732 TokenTree::Punct(p) if p.as_char() == ',' => {}
733 _ => errs.push(construct_err(
734 "expected comma separator between enum variants",
735 token.span(),
736 )),
737 }
738
739 match iter.next() {
740 Some(tt) => tt,
741 None => break,
742 }
743 };
744
745 let name = match token {
746 TokenTree::Ident(i) => i,
747 _ => {
748 errs.push(construct_err(
749 "expected variant name identifier",
750 token.span(),
751 ));
752 proc_macro::Ident::new("Var", Span::call_site())
753 }
754 };
755
756 let mut kind = VariantKind::Unit;
757 match iter.peek() {
758 Some(TokenTree::Punct(p)) if p.as_char() == ',' => {}
759 Some(TokenTree::Group(g))
760 if g.delimiter() == Delimiter::Parenthesis =>
761 {
762 let stream = g.stream().into_iter();
763 iter.next();
764
765 let mut ty = TokenStream::new();
766
767 for token in stream {
768 ty.extend([token]);
769 }
770
771 if !ty.is_empty() {
772 kind = VariantKind::Unnamed(ty);
773 }
774 }
775
776 Some(TokenTree::Group(g))
777 if g.delimiter() == Delimiter::Brace =>
778 {
779 let mut stream = g.stream().into_iter();
780 iter.next();
781
782 let token = stream.next();
783 let name = match token {
784 Some(TokenTree::Ident(i)) => i,
785 _ => {
786 errs.push(construct_err(
787 "expected field name identifier",
788 Span::call_site(),
789 ));
790 proc_macro::Ident::new("Var", Span::call_site())
791 }
792 };
793
794 match stream.next() {
795 Some(TokenTree::Punct(p)) if p.as_char() == ':' => {}
796 Some(tt) =>
797 errs.push(construct_err(
798 &format!("expected field name colon separator, found: {}", tt),
799 tt.span()
800 )),
801 _ =>
802 errs.push(construct_err(
803 "expected field name colon separator",
804 Span::call_site(),
805 )),
806 }
807
808 let mut ty = TokenStream::new();
809
810 for token in stream {
811 ty.extend([token]);
812 }
813
814 if !ty.is_empty() {
815 kind = VariantKind::Named(name, ty);
816 }
817 }
818
819 None => {}
820
821 Some(tt) => errs.push(construct_err(
822 "expected named or unnamed variant",
823 tt.span(),
824 )),
825 }
826
827 out.push(Variant { name, kind });
828 }
829
830 out
831 }
832}
833
834#[derive(Default)]
835struct ResExtArgs {
836 prefix: Option<String>,
837 suffix: Option<String>,
838 msg_prefix: Option<String>,
839 msg_suffix: Option<String>,
840 delimiter: Option<String>,
841 source_prefix: Option<String>,
842 include_variant: bool,
843 alias: Option<String>,
844 buf_size: Option<usize>,
845 alloc: bool,
846}
847
848fn parse_args(
849 input: TokenStream,
850 errs: &mut Vec<TokenStream>,
851) -> Option<ResExtArgs> {
852 let mut args = ResExtArgs {
853 prefix: None,
854 suffix: None,
855 msg_prefix: None,
856 msg_suffix: None,
857 delimiter: None,
858 source_prefix: None,
859 include_variant: false,
860 alias: None,
861 buf_size: None,
862 alloc: false,
863 };
864
865 let mut iter = input.into_iter().peekable();
866
867 while let Some(token) = iter.next() {
868 let key = match token {
869 TokenTree::Ident(ident) => Some(ident),
870 _ => {
871 errs.push(construct_err(
872 &format!("expected key identifier, found: {}", token),
873 token.span(),
874 ));
875 None
876 }
877 };
878
879 let token = iter.next();
880 match token {
881 Some(TokenTree::Punct(p)) if p.as_char() == '=' => {}
882 _ => {
883 errs.push(construct_err(
884 &format!(
885 "expected '=' separator between key and value, found: '{}'",
886 token.as_ref().map(|s| s.to_string()).unwrap_or_default()
887 ),
888 token.map(|t| t.span()).unwrap_or(Span::call_site()),
889 ));
890 }
891 }
892
893 let val = iter.next();
894
895 if val.is_none() {
896 errs.push(construct_err(
897 "expected value after '='",
898 Span::call_site(),
899 ));
900 }
901
902 if let Some(key) = key {
903 match key.to_string().as_str() {
904 "prefix" => {
905 args.prefix = val
906 .map(|s| s.to_string().trim_matches('"').to_string());
907 }
908
909 "suffix" => {
910 args.suffix = val
911 .map(|s| s.to_string().trim_matches('"').to_string());
912 }
913
914 "msg_prefix" => {
915 args.msg_prefix = val
916 .map(|s| s.to_string().trim_matches('"').to_string());
917 }
918
919 "msg_suffix" => {
920 args.msg_suffix = val
921 .map(|s| s.to_string().trim_matches('"').to_string());
922 }
923
924 "delimiter" => {
925 args.delimiter = val
926 .map(|s| s.to_string().trim_matches('"').to_string());
927 }
928
929 "source_prefix" => {
930 args.source_prefix = val
931 .map(|s| s.to_string().trim_matches('"').to_string());
932 }
933
934 "include_variant" => {
935 let v = val
936 .as_ref()
937 .map(|t| t.to_string())
938 .unwrap_or_default()
939 .parse();
940
941 args.include_variant = match v {
942 Ok(v) => v,
943 Err(err) => {
944 errs.push(construct_err(&format!("invalid bool value for `include_variant` attribute\nError: {}", err), val.map(|v| v.span()).unwrap_or_else(Span::call_site)));
945
946 false
948 }
949 };
950 }
951
952 "alias" => {
953 args.alias = val
954 .map(|s| s.to_string().trim_matches('"').to_string());
955 }
956
957 "buf_size" => {
958 let v = val
959 .as_ref()
960 .map(|t| t.to_string())
961 .unwrap_or_default()
962 .parse();
963
964 args.buf_size = match v {
965 Ok(v) => Some(v),
966 Err(err) => {
967 errs.push(construct_err(&format!("invalid usize value for `buf_size` attribute\nError: {}", err), val.map(|v| v.span()).unwrap_or_else(Span::call_site) ));
968
969 None
970 }
971 };
972 }
973
974 "alloc" => {
975 let v = val
976 .as_ref()
977 .map(|t| t.to_string())
978 .unwrap_or_default()
979 .parse();
980
981 args.alloc = match v {
982 Ok(v) => v,
983 Err(err) => {
984 errs.push(construct_err(&format!("invalid bool value for `alloc` attribute\nError: {}", err), val.map(|v| v.span()).unwrap_or_else(Span::call_site)));
985
986 false
988 }
989 };
990 }
991
992 _ => errs.push(construct_err(
993 &format!("unrecognized attribute: {}", &key),
994 key.span(),
995 )),
996 }
997 }
998
999 match iter.peek() {
1000 Some(TokenTree::Punct(p)) if p.as_char() == ',' => {
1001 iter.next();
1002 }
1003 Some(TokenTree::Ident(_)) => {}
1004 None => {}
1005 _ => {
1006 errs.push(
1007 construct_err(
1008 &format!("expected comma, newline or whitespace delimiter, found: '{}'", iter.peek().map(|t| t.to_string()).unwrap_or_default()),
1009 iter.peek().map(|t| t.span()).unwrap_or_else(Span::call_site)
1010 ),
1011 );
1012 iter.next();
1013 }
1014 }
1015 }
1016
1017 if !errs.is_empty() { None } else { Some(args) }
1018}
1019
1020fn construct_err(msg: &str, span: Span) -> TokenStream {
1021 let mut ts = TokenStream::new();
1022 ts.extend([TokenTree::Ident(proc_macro::Ident::new(
1023 "compile_error",
1024 span,
1025 ))]);
1026
1027 ts.extend([TokenTree::Punct(Punct::new('!', Spacing::Alone))]);
1028
1029 let mut inner = TokenStream::new();
1030 let mut lit = Literal::string(msg);
1031 lit.set_span(span);
1032 inner.extend([TokenTree::Literal(lit)]);
1033
1034 ts.extend([TokenTree::Group(Group::new(Delimiter::Parenthesis, inner))]);
1035 ts.extend([TokenTree::Punct(Punct::new(';', Spacing::Alone))]);
1036
1037 ts
1038}