1use std::ffi::{c_void, CStr};
41use std::fmt;
42use std::io::{BufRead, Write};
43use std::os::raw::{c_char, c_int};
44use std::panic::{catch_unwind, AssertUnwindSafe};
45use std::ptr::NonNull;
46
47use crate::{ffi, Error, Format, RuntimeId};
48
49#[derive(Clone, Debug, Default, Eq, PartialEq)]
51pub struct Description {
52 pub name: String,
54 pub extensions: Vec<String>,
56 pub aliases: Vec<String>,
57 pub write: bool,
59 pub author: bool,
62 pub syntax: Option<String>,
66 pub features: Vec<Feature>,
67 pub sets: Vec<Set>,
68 pub samples: Vec<String>,
71 pub feature_samples: Vec<Sample>,
73}
74
75#[derive(Clone, Debug, Default, Eq, PartialEq)]
77pub struct Feature {
78 pub name: String,
80 pub default: bool,
82 pub requires: Vec<String>,
84 pub syntax: Option<String>,
88}
89
90#[derive(Clone, Debug, Default, Eq, PartialEq)]
92pub struct Set {
93 pub name: String,
94 pub extensions: Vec<String>,
95 pub aliases: Vec<String>,
96 pub features: Vec<String>,
97}
98
99#[derive(Clone, Debug, Default, Eq, PartialEq)]
101pub struct Sample {
102 pub text: String,
103 pub features: Vec<String>,
104}
105
106impl Description {
107 fn to_json(&self) -> String {
109 let mut out = String::from("{\"name\":");
110 json_string(&self.name, &mut out);
111 for (key, list) in [
112 ("extensions", &self.extensions),
113 ("aliases", &self.aliases),
114 ] {
115 out.push_str(",\"");
116 out.push_str(key);
117 out.push_str("\":");
118 json_strings(list, &mut out);
119 }
120 out.push_str(",\"caps\":{\"read\":true,\"write\":");
121 out.push_str(if self.write { "true" } else { "false" });
122 out.push_str(",\"author\":");
123 out.push_str(if self.author { "true" } else { "false" });
124 out.push('}');
125 if let Some(syntax) = &self.syntax {
126 out.push_str(",\"syntax\":");
127 out.push_str(syntax);
128 }
129 out.push_str(",\"features\":[");
130 for (i, f) in self.features.iter().enumerate() {
131 if i > 0 {
132 out.push(',');
133 }
134 out.push_str("{\"name\":");
135 json_string(&f.name, &mut out);
136 out.push_str(",\"default\":");
137 out.push_str(if f.default { "true" } else { "false" });
138 out.push_str(",\"requires\":");
139 json_strings(&f.requires, &mut out);
140 if let Some(syntax) = &f.syntax {
141 out.push_str(",\"syntax\":");
142 out.push_str(syntax);
143 }
144 out.push('}');
145 }
146 out.push_str("],\"sets\":[");
147 for (i, set) in self.sets.iter().enumerate() {
148 if i > 0 {
149 out.push(',');
150 }
151 out.push_str("{\"name\":");
152 json_string(&set.name, &mut out);
153 for (key, list) in [
154 ("extensions", &set.extensions),
155 ("aliases", &set.aliases),
156 ("features", &set.features),
157 ] {
158 out.push_str(",\"");
159 out.push_str(key);
160 out.push_str("\":");
161 json_strings(list, &mut out);
162 }
163 out.push('}');
164 }
165 out.push_str("],\"samples\":[");
166 let mut first = true;
167 for text in &self.samples {
168 if !first {
169 out.push(',');
170 }
171 first = false;
172 json_string(text, &mut out);
173 }
174 for sample in &self.feature_samples {
175 if !first {
176 out.push(',');
177 }
178 first = false;
179 out.push_str("{\"text\":");
180 json_string(&sample.text, &mut out);
181 out.push_str(",\"features\":");
182 json_strings(&sample.features, &mut out);
183 out.push('}');
184 }
185 out.push_str("]}");
186 out
187 }
188}
189
190fn json_strings(list: &[String], out: &mut String) {
191 out.push('[');
192 for (i, item) in list.iter().enumerate() {
193 if i > 0 {
194 out.push(',');
195 }
196 json_string(item, out);
197 }
198 out.push(']');
199}
200
201fn json_string(s: &str, out: &mut String) {
202 out.push('"');
203 for c in s.chars() {
204 match c {
205 '"' => out.push_str("\\\""),
206 '\\' => out.push_str("\\\\"),
207 '\n' => out.push_str("\\n"),
208 '\r' => out.push_str("\\r"),
209 '\t' => out.push_str("\\t"),
210 c if (c as u32) < 0x20 => out.push_str(&format!("\\u{:04x}", c as u32)),
211 c => out.push(c),
212 }
213 }
214 out.push('"');
215}
216
217#[derive(Clone, Copy, Debug)]
221pub struct Call<'a> {
222 pub row: &'a str,
223 pub features: &'a [&'a str],
224}
225
226impl Call<'_> {
227 pub fn has(&self, feature: &str) -> bool {
229 self.features.contains(&feature)
230 }
231}
232
233pub trait Language: Send + Sync + 'static {
240 fn describe(&self) -> Description;
241
242 fn parse(&self, row: &str, source: &[u8]) -> Result<Vec<u8>, String> {
246 let _ = (row, source);
247 Err("this language implements neither parse nor parse_with".to_owned())
248 }
249
250 fn print(&self, row: &str, table: &[u8]) -> Result<Vec<u8>, String> {
253 let _ = (row, table);
254 Err("this language does not print".to_owned())
255 }
256
257 fn parse_with(&self, call: &Call<'_>, source: &[u8]) -> Result<Vec<u8>, String> {
259 self.parse(call.row, source)
260 }
261
262 fn print_with(&self, call: &Call<'_>, table: &[u8]) -> Result<Vec<u8>, String> {
264 self.print(call.row, table)
265 }
266
267 fn render(&self, call: &Call<'_>, request: &[u8]) -> Result<Vec<u8>, String> {
283 const BLOCK: &[u8] = br#"{"which":"render_block","table":"#;
286 if request.starts_with(BLOCK) && request.ends_with(b"}") {
287 return self.print_with(call, &request[BLOCK.len()..request.len() - 1]);
288 }
289 Err("this language names no renderers but render_block".to_owned())
290 }
291}
292
293#[derive(Clone, Debug, Eq, PartialEq)]
295pub struct RegisterError {
296 pub error: Error,
299 pub message: String,
301}
302
303impl fmt::Display for RegisterError {
304 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
305 if self.message.is_empty() {
306 write!(f, "{}", self.error)
307 } else {
308 write!(f, "{}: {}", self.error, self.message)
309 }
310 }
311}
312
313impl std::error::Error for RegisterError {}
314
315struct Hosted<L> {
318 language: L,
319 features: Vec<String>,
320}
321
322fn vtable<L: Language>(hosted: *mut Hosted<L>, json: &str, description: &Description) -> ffi::TwigLanguageVTable {
324 ffi::TwigLanguageVTable {
325 version: ffi::TWIG_LANGUAGE_VTABLE_VERSION,
326 user_data: hosted as *mut c_void,
327 description: json.as_ptr(),
328 description_len: json.len(),
329 parse: Some(parse_trampoline::<L>),
330 print: if description.write {
331 Some(print_trampoline::<L>)
332 } else {
333 None
334 },
335 render: Some(render_trampoline::<L>),
336 free: Some(free_trampoline),
337 }
338}
339
340fn host<L: Language>(language: L, description: &Description) -> *mut Hosted<L> {
341 Box::into_raw(Box::new(Hosted {
342 language,
343 features: description.features.iter().map(|f| f.name.clone()).collect(),
344 }))
345}
346
347fn message(err: &[c_char]) -> String {
348 unsafe { CStr::from_ptr(err.as_ptr()) }
349 .to_string_lossy()
350 .into_owned()
351}
352
353pub fn register<L: Language>(language: L) -> Result<Format, RegisterError> {
357 let description = language.describe();
358 let json = description.to_json();
359 let hosted = host(language, &description);
360 let vtable = vtable(hosted, &json, &description);
361 let mut code: c_int = 0;
362 let mut err = [0 as c_char; 512];
363 let status =
364 unsafe { ffi::twig_language_register(&vtable, &mut code, err.as_mut_ptr(), err.len()) };
365 match Error::from_status(status) {
366 Ok(()) => Ok(Format::Runtime(RuntimeId(code))),
367 Err(error) => {
368 drop(unsafe { Box::from_raw(hosted) });
370 Err(RegisterError {
371 error,
372 message: message(&err),
373 })
374 }
375 }
376}
377
378pub struct Server {
382 raw: NonNull<ffi::TwigServer>,
383 free: unsafe fn(*mut c_void),
385 hosted: *mut c_void,
386}
387
388unsafe impl Send for Server {}
391
392unsafe fn drop_hosted<L>(p: *mut c_void) {
393 drop(unsafe { Box::from_raw(p as *mut Hosted<L>) });
394}
395
396impl Server {
397 pub fn new<L: Language>(language: L) -> Result<Server, RegisterError> {
400 let description = language.describe();
401 let json = description.to_json();
402 let hosted = host(language, &description);
403 let vtable = vtable(hosted, &json, &description);
404 let mut raw = std::ptr::null_mut();
405 let mut err = [0 as c_char; 512];
406 let status =
407 unsafe { ffi::twig_server_create(&vtable, &mut raw, err.as_mut_ptr(), err.len()) };
408 match Error::from_status(status).and_then(|()| NonNull::new(raw).ok_or(Error::Internal)) {
409 Ok(raw) => Ok(Server {
410 raw,
411 free: drop_hosted::<L>,
412 hosted: hosted as *mut c_void,
413 }),
414 Err(error) => {
415 drop(unsafe { Box::from_raw(hosted) });
416 Err(RegisterError {
417 error,
418 message: message(&err),
419 })
420 }
421 }
422 }
423
424 pub fn handle(&mut self, request: &[u8]) -> Result<Vec<u8>, Error> {
428 let mut ptr: *const u8 = std::ptr::null();
429 let mut len = 0usize;
430 let status = unsafe {
431 ffi::twig_server_handle(self.raw.as_ptr(), request.as_ptr(), request.len(), &mut ptr, &mut len)
432 };
433 Error::from_status(status)?;
434 Ok(unsafe { bytes(ptr, len) }.to_vec())
435 }
436}
437
438impl Drop for Server {
439 fn drop(&mut self) {
440 unsafe {
441 ffi::twig_server_destroy(self.raw.as_ptr());
442 (self.free)(self.hosted);
443 }
444 }
445}
446
447pub fn serve<L: Language>(language: L) -> std::io::Result<()> {
461 let mut server = Server::new(language)
462 .map_err(|e| std::io::Error::new(std::io::ErrorKind::InvalidData, e.to_string()))?;
463 let stdin = std::io::stdin();
464 let mut stdout = std::io::stdout().lock();
465 for line in stdin.lock().split(b'\n') {
466 let mut line = line?;
467 if line.last() == Some(&b'\r') {
468 line.pop();
469 }
470 let response = server
471 .handle(&line)
472 .map_err(|e| std::io::Error::other(e.to_string()))?;
473 stdout.write_all(&response)?;
474 stdout.write_all(b"\n")?;
475 stdout.flush()?;
476 }
477 Ok(())
478}
479
480pub(crate) unsafe fn bytes<'a>(ptr: *const u8, len: usize) -> &'a [u8] {
481 if len == 0 || ptr.is_null() {
482 &[]
483 } else {
484 unsafe { std::slice::from_raw_parts(ptr, len) }
485 }
486}
487
488pub(crate) unsafe fn give(bytes: Vec<u8>, out: *mut *mut u8, out_len: *mut usize) {
489 let boxed = bytes.into_boxed_slice();
490 unsafe {
491 *out_len = boxed.len();
492 *out = Box::into_raw(boxed) as *mut u8;
493 }
494}
495
496unsafe fn call(
500 f: impl FnOnce() -> Result<Vec<u8>, String>,
501 out: *mut *mut u8,
502 out_len: *mut usize,
503) -> c_int {
504 match catch_unwind(AssertUnwindSafe(f)) {
505 Ok(Ok(answer)) => {
506 unsafe { give(answer, out, out_len) };
507 0
508 }
509 Ok(Err(message)) => {
510 unsafe { give(message.into_bytes(), out, out_len) };
511 1
512 }
513 Err(_) => {
514 unsafe { give(b"the language panicked".to_vec(), out, out_len) };
515 1
516 }
517 }
518}
519
520unsafe fn with_call<L: Language>(
522 user_data: *mut c_void,
523 record: *const ffi::TwigLanguageCall,
524 out: *mut *mut u8,
525 out_len: *mut usize,
526 f: impl FnOnce(&L, &Call<'_>, &[u8]) -> Result<Vec<u8>, String>,
527) -> c_int {
528 let hosted = unsafe { &*(user_data as *const Hosted<L>) };
529 let record = unsafe { &*record };
530 let row = std::str::from_utf8(unsafe { bytes(record.row, record.row_len) }).unwrap_or("");
531 let input = unsafe { bytes(record.input, record.input_len) };
532 let features: Vec<&str> = hosted
533 .features
534 .iter()
535 .enumerate()
536 .filter(|(i, _)| *i < 32 && record.features & (1u32 << i) != 0)
537 .map(|(_, name)| name.as_str())
538 .collect();
539 let c = Call {
540 row,
541 features: &features,
542 };
543 unsafe { call(|| f(&hosted.language, &c, input), out, out_len) }
544}
545
546unsafe extern "C" fn parse_trampoline<L: Language>(
547 user_data: *mut c_void,
548 record: *const ffi::TwigLanguageCall,
549 out: *mut *mut u8,
550 out_len: *mut usize,
551) -> c_int {
552 unsafe { with_call::<L>(user_data, record, out, out_len, |l, c, input| l.parse_with(c, input)) }
553}
554
555unsafe extern "C" fn print_trampoline<L: Language>(
556 user_data: *mut c_void,
557 record: *const ffi::TwigLanguageCall,
558 out: *mut *mut u8,
559 out_len: *mut usize,
560) -> c_int {
561 unsafe { with_call::<L>(user_data, record, out, out_len, |l, c, input| l.print_with(c, input)) }
562}
563
564unsafe extern "C" fn render_trampoline<L: Language>(
565 user_data: *mut c_void,
566 record: *const ffi::TwigLanguageCall,
567 out: *mut *mut u8,
568 out_len: *mut usize,
569) -> c_int {
570 unsafe { with_call::<L>(user_data, record, out, out_len, |l, c, input| l.render(c, input)) }
571}
572
573pub(crate) unsafe extern "C" fn free_trampoline(_: *mut c_void, ptr: *mut u8, len: usize) {
574 if !ptr.is_null() {
575 drop(unsafe { Box::from_raw(std::ptr::slice_from_raw_parts_mut(ptr, len)) });
576 }
577}
578
579#[cfg(test)]
580mod tests {
581 use super::*;
582 use crate::{Document, Gesture, Target};
583
584 struct Lines(&'static str);
588
589 fn escape(s: &str) -> String {
590 let mut out = String::new();
591 json_string(s, &mut out);
592 out
593 }
594
595 impl Language for Lines {
596 fn describe(&self) -> Description {
597 Description {
598 name: self.0.to_owned(),
599 extensions: vec![format!("{}-ext", self.0)],
600 write: true,
601 samples: vec!["one\ntwo\n".to_owned(), "x\n".to_owned()],
602 ..Description::default()
603 }
604 }
605
606 fn parse(&self, _row: &str, source: &[u8]) -> Result<Vec<u8>, String> {
607 let src = std::str::from_utf8(source).map_err(|_| "not UTF-8".to_owned())?;
608 if src.contains('\0') {
609 return Err("a NUL byte is not a line".to_owned());
610 }
611 let mut rows = vec![format!("{{\"kind\":\"doc\",\"span\":[0,{}]}}", src.len())];
612 let mut start = 0;
613 for line in src.split_inclusive('\n') {
614 let text = line.trim_end_matches('\n').trim_end_matches('\r');
615 if !text.is_empty() {
616 let para = rows.len();
617 let end = start + text.len();
618 rows.push(format!(
619 "{{\"kind\":\"para\",\"parent\":0,\"span\":[{start},{end}]}}"
620 ));
621 rows.push(format!(
622 "{{\"kind\":\"str\",\"parent\":{para},\"span\":[{start},{end}],\"text\":{}}}",
623 escape(text)
624 ));
625 }
626 start += line.len();
627 }
628 Ok(format!("{{\"nodes\":[{}]}}", rows.join(",")).into_bytes())
629 }
630
631 fn print(&self, _row: &str, table: &[u8]) -> Result<Vec<u8>, String> {
632 let table = std::str::from_utf8(table).map_err(|e| e.to_string())?;
633 let mut out = String::new();
634 let mut rest = table;
635 while let Some(at) = rest.find("\"text\":\"") {
636 rest = &rest[at + 8..];
637 let end = rest.find('"').ok_or("unterminated text")?;
638 out.push_str(&rest[..end]);
639 out.push('\n');
640 rest = &rest[end..];
641 }
642 Ok(out.into_bytes())
643 }
644 }
645
646 #[test]
647 fn a_registered_language_is_a_format_like_any_other() {
648 let format = register(Lines("rust-lines")).expect("register");
649 assert!(matches!(format, Format::Runtime(_)));
650 assert_eq!(format.name(), "rust-lines");
651 assert_eq!(Format::by_name("rust-lines"), Some(format));
652 assert_eq!(Format::by_name("gfm"), Some(Format::Gfm));
653 assert_eq!(Format::Gfm.name(), "gfm");
654 assert_eq!(Target::from(format).as_format(), Some(format));
655
656 let mut doc = Document::parse_str("alpha\nbeta\n", format).expect("parse");
657 assert_eq!(doc.render_html().unwrap(), b"<p>alpha</p>\n<p>beta</p>\n");
658 assert_eq!(
659 doc.serialize_to(Target::Markdown).unwrap(),
660 b"alpha\n\nbeta\n"
661 );
662 assert_eq!(
663 doc.serialize_to(Target::from(format)).unwrap(),
664 b"alpha\nbeta\n"
665 );
666
667 let mut md = Document::parse_str("# T\n\nx\n", Format::Markdown).expect("markdown");
668 assert_eq!(md.serialize_to(Target::from(format)).unwrap(), b"T\nx\n");
669
670 assert!(!format.supports(Gesture::SetBlock));
672 assert!(!format.supports(Gesture::InsertLink));
673
674 assert_eq!(
676 Document::parse_str("a\0b", format).err(),
677 Some(Error::ParseError)
678 );
679 }
680
681 #[test]
682 fn a_refusal_says_why_and_registers_nothing() {
683 struct Taken;
684 impl Language for Taken {
685 fn describe(&self) -> Description {
686 Description {
687 name: "markdown".into(),
688 samples: vec!["x".into()],
689 ..Description::default()
690 }
691 }
692 fn parse(&self, _: &str, _: &[u8]) -> Result<Vec<u8>, String> {
693 Ok(br#"{"nodes":[{"kind":"doc","span":[0,1]}]}"#.to_vec())
694 }
695 }
696 let err = register(Taken).unwrap_err();
697 assert_eq!(err.error, Error::InvalidLanguage);
698 assert!(
699 err.message.contains("already a format's"),
700 "{}",
701 err.message
702 );
703
704 struct Panics;
705 impl Language for Panics {
706 fn describe(&self) -> Description {
707 Description {
708 name: "rust-panics".into(),
709 samples: vec!["x".into()],
710 ..Description::default()
711 }
712 }
713 fn parse(&self, _: &str, _: &[u8]) -> Result<Vec<u8>, String> {
714 panic!("boom")
715 }
716 }
717 let err = register(Panics).unwrap_err();
718 assert!(
719 err.message.contains("the language panicked"),
720 "{}",
721 err.message
722 );
723 assert_eq!(Format::by_name("rust-panics"), None);
724 }
725
726 struct CrlfLines;
730
731 impl Language for CrlfLines {
732 fn describe(&self) -> Description {
733 Description {
734 name: "rust-crlf".into(),
735 write: true,
736 author: true,
737 syntax: Some(r#"{"renderers":["render_text"]}"#.into()),
738 features: vec![Feature {
739 name: "crlf".into(),
740 ..Feature::default()
741 }],
742 sets: vec![Set {
743 name: "rust-crlf-crlf".into(),
744 features: vec!["crlf".into()],
745 ..Set::default()
746 }],
747 samples: vec!["one\ntwo\n".into()],
748 feature_samples: vec![Sample {
749 text: "x\n".into(),
750 features: vec!["crlf".into()],
751 }],
752 ..Description::default()
753 }
754 }
755
756 fn parse_with(&self, call: &Call<'_>, source: &[u8]) -> Result<Vec<u8>, String> {
757 Lines("rust-crlf").parse(call.row, source)
758 }
759
760 fn print_with(&self, call: &Call<'_>, table: &[u8]) -> Result<Vec<u8>, String> {
761 let out = Lines("rust-crlf").print(call.row, table)?;
762 Ok(if call.has("crlf") {
763 String::from_utf8_lossy(&out).replace('\n', "\r\n").into_bytes()
764 } else {
765 out
766 })
767 }
768
769 fn render(&self, _: &Call<'_>, request: &[u8]) -> Result<Vec<u8>, String> {
772 let request = std::str::from_utf8(request).map_err(|e| e.to_string())?;
773 let at = request.find("\"text\":\"").ok_or("no text")? + 8;
774 let mut out = String::new();
775 let mut chars = request[at..].chars();
776 while let Some(c) = chars.next() {
777 match c {
778 '"' => return Ok(out.into_bytes()),
779 '\\' => out.push(chars.next().ok_or("unterminated escape")?),
780 c => out.push(c),
781 }
782 }
783 Err("unterminated text".to_owned())
784 }
785 }
786
787 #[test]
788 fn features_sets_and_renderers_reach_every_entry_point() {
789 let format = register(CrlfLines).expect("register");
790 let set = Format::by_name("rust-crlf-crlf").expect("the set is a format");
791 assert_ne!(set, format);
792 assert_eq!(set.dialect_of(), Some(format));
793 assert_eq!(format.dialect_of(), None);
794 assert_eq!(format.feature_flags(&["crlf"]), Ok(1));
795 assert_eq!(format.feature_flags(&["tabs"]), Err(Error::NotFound));
796 assert_eq!(Format::Djot.feature_flags(&["math"]), Err(Error::UnsupportedFormat));
797
798 let mut doc = Document::parse_with_features(b"a\nb\n", format, &["crlf"]).unwrap();
799 assert_eq!(doc.serialize_to(Target::from(format)).unwrap(), b"a\r\nb\r\n");
800 let mut doc = Document::parse_str("a\nb\n", set).unwrap();
801 assert_eq!(doc.serialize_to(Target::from(set)).unwrap(), b"a\r\nb\r\n");
802 let mut doc = Document::parse_str("a\nb\n", format).unwrap();
803 assert_eq!(doc.serialize_to(Target::from(format)).unwrap(), b"a\nb\n");
804
805 assert!(format.supports(Gesture::InsertLiteral));
807 assert!(format.supports_with_features(&["crlf"], Gesture::InsertLiteral));
808 assert!(!format.supports_with_features(&["tabs"], Gesture::InsertLiteral));
809 let mut editor = crate::Editor::new_with_features(b"a\n", format, &["crlf"]).unwrap();
810 editor.insert_literal(0, "*x* ").unwrap();
811 assert_eq!(editor.source().unwrap(), b"*x* a\n");
812 }
813
814 #[test]
815 fn a_server_answers_the_wire_for_a_language() {
816 let mut server = Server::new(CrlfLines).expect("serve");
817 let described = server.handle(br#"{"op":"describe"}"#).unwrap();
818 let described = String::from_utf8(described).unwrap();
819 assert!(described.starts_with(r#"{"ok":true,"description":{"name":"rust-crlf""#), "{described}");
820 let printed = server
821 .handle(br#"{"op":"print","dialect":"rust-crlf","features":["crlf"],"table":{"nodes":[{"kind":"doc"},{"kind":"para","parent":0},{"kind":"str","parent":1,"text":"hi"}]}}"#)
822 .unwrap();
823 assert_eq!(printed, br#"{"ok":true,"output":"hi\r\n"}"#);
824 let refused = server.handle(b"not json").unwrap();
825 assert!(refused.starts_with(br#"{"ok":false"#));
826
827 struct Unreadable;
828 impl Language for Unreadable {
829 fn describe(&self) -> Description {
830 Description {
831 name: "rust-unreadable".into(),
832 syntax: Some("not json".into()),
833 samples: vec!["x".into()],
834 ..Description::default()
835 }
836 }
837 }
838 let err = Server::new(Unreadable).err().expect("refused");
839 assert_eq!(err.error, Error::InvalidLanguage);
840 }
841
842}