1use std::collections::hash_map::DefaultHasher;
2use std::hash::{Hash, Hasher};
3
4use cha_core::{ClassInfo, FunctionInfo, ImportInfo, SourceFile, SourceModel};
5use tree_sitter::{Node, Parser};
6
7use crate::LanguageParser;
8
9pub struct CParser;
10pub struct CppParser;
11
12impl LanguageParser for CParser {
13 fn language_name(&self) -> &str {
14 "c"
15 }
16 fn parse(&self, file: &SourceFile) -> Option<SourceModel> {
17 parse_c_like(file, "c", &tree_sitter_c::LANGUAGE.into())
18 }
19}
20
21impl LanguageParser for CppParser {
22 fn language_name(&self) -> &str {
23 "cpp"
24 }
25 fn parse(&self, file: &SourceFile) -> Option<SourceModel> {
26 parse_c_like(file, "cpp", &tree_sitter_cpp::LANGUAGE.into())
27 }
28}
29
30fn parse_c_like(
31 file: &SourceFile,
32 lang: &str,
33 language: &tree_sitter::Language,
34) -> Option<SourceModel> {
35 let mut parser = Parser::new();
36 parser.set_language(language).ok()?;
37 let tree = parser.parse(&file.content, None)?;
38 let root = tree.root_node();
39 let src = file.content.as_bytes();
40
41 let mut functions = Vec::new();
42 let mut classes = Vec::new();
43 let mut imports = Vec::new();
44 let mut type_aliases = Vec::new();
45
46 let imports_map = crate::c_imports::build(root, src);
47 collect_top_level(
48 root,
49 src,
50 &imports_map,
51 &mut functions,
52 &mut classes,
53 &mut imports,
54 &mut type_aliases,
55 );
56
57 if is_header_file(file) {
62 for f in &mut functions {
63 f.is_exported = true;
64 }
65 }
66
67 Some(SourceModel {
68 language: lang.into(),
69 total_lines: file.line_count(),
70 functions,
71 classes,
72 imports,
73 comments: collect_comments(root, src),
74 type_aliases,
75 })
76}
77
78fn is_header_file(file: &SourceFile) -> bool {
79 file.path
80 .extension()
81 .is_some_and(|e| e == "h" || e == "hxx" || e == "hpp")
82}
83
84fn collect_top_level(
87 root: Node,
88 src: &[u8],
89 imports_map: &crate::type_ref::ImportsMap,
90 functions: &mut Vec<FunctionInfo>,
91 classes: &mut Vec<ClassInfo>,
92 imports: &mut Vec<ImportInfo>,
93 type_aliases: &mut Vec<(String, String)>,
94) {
95 let mut cursor = root.walk();
96 for child in root.children(&mut cursor) {
97 match child.kind() {
98 "function_definition" => {
99 handle_function_definition(child, src, imports_map, functions, classes);
100 }
101 "declaration" => {
102 if has_function_declarator(child)
110 && let Some(f) = extract_function(child, src, imports_map)
111 {
112 functions.push(f);
113 }
114 }
115 "struct_specifier" | "class_specifier" => {
116 if let Some(c) = extract_class(child, src) {
117 classes.push(c);
118 }
119 }
120 "type_definition" => {
121 extract_typedef_struct(child, src, classes, type_aliases);
122 }
123 "preproc_include" => {
124 if let Some(imp) = extract_include(child, src) {
125 imports.push(imp);
126 }
127 }
128 "namespace_definition" | "linkage_specification" | "template_declaration" => {
135 collect_top_level(
136 child,
137 src,
138 imports_map,
139 functions,
140 classes,
141 imports,
142 type_aliases,
143 );
144 }
145 _ => {
146 if child.child_count() > 0 {
147 collect_top_level(
148 child,
149 src,
150 imports_map,
151 functions,
152 classes,
153 imports,
154 type_aliases,
155 );
156 }
157 }
158 }
159 }
160}
161
162fn handle_function_definition(
166 node: Node,
167 src: &[u8],
168 imports_map: &crate::type_ref::ImportsMap,
169 functions: &mut Vec<FunctionInfo>,
170 classes: &mut Vec<ClassInfo>,
171) {
172 if let Some(c) = try_extract_macro_class(node, src) {
173 classes.push(c);
174 return;
175 }
176 let Some(f) = extract_function(node, src, imports_map) else {
177 return;
178 };
179 if let Some(q) = crate::cpp::extract_class_qualifier(node, src) {
180 crate::cpp::attach_to_class(&q, classes);
181 }
182 functions.push(f);
183}
184
185fn extract_typedef_struct(
186 node: Node,
187 src: &[u8],
188 classes: &mut Vec<ClassInfo>,
189 type_aliases: &mut Vec<(String, String)>,
190) {
191 let found_struct = register_typedef_struct_children(node, src, classes, type_aliases);
192 if !found_struct {
193 register_simple_typedef(node, src, type_aliases);
194 }
195}
196
197fn register_typedef_struct_children(
198 node: Node,
199 src: &[u8],
200 classes: &mut Vec<ClassInfo>,
201 type_aliases: &mut Vec<(String, String)>,
202) -> bool {
203 let mut found_struct = false;
204 let mut inner = node.walk();
205 for sub in node.children(&mut inner) {
206 if sub.kind() != "struct_specifier" && sub.kind() != "class_specifier" {
207 continue;
208 }
209 found_struct = true;
210 register_single_typedef_struct(node, sub, src, classes, type_aliases);
211 }
212 found_struct
213}
214
215fn register_single_typedef_struct(
216 typedef: Node,
217 sub: Node,
218 src: &[u8],
219 classes: &mut Vec<ClassInfo>,
220 type_aliases: &mut Vec<(String, String)>,
221) {
222 let Some(mut c) = extract_class(sub, src) else {
223 return;
224 };
225 let original_name = c.name.clone();
226 if c.name.is_empty()
227 && let Some(decl) = typedef.child_by_field_name("declarator")
228 {
229 c.name = node_text(decl, src).to_string();
230 }
231 if !original_name.is_empty()
232 && let Some(decl) = typedef.child_by_field_name("declarator")
233 {
234 let alias = node_text(decl, src).to_string();
235 if alias != original_name {
236 type_aliases.push((alias, original_name));
237 }
238 }
239 if !c.name.is_empty() {
240 classes.push(c);
241 }
242}
243
244fn register_simple_typedef(node: Node, src: &[u8], type_aliases: &mut Vec<(String, String)>) {
246 let alias = extract_typedef_alias(node, src);
247 let original = node
248 .child_by_field_name("type")
249 .map(|t| node_text(t, src).trim().to_string())
250 .unwrap_or_default();
251 if !alias.is_empty() && alias != original {
252 type_aliases.push((alias, original));
253 }
254}
255
256fn extract_typedef_alias(node: Node, src: &[u8]) -> String {
261 if let Some(decl) = node.child_by_field_name("declarator") {
262 return node_text(decl, src).trim().to_string();
263 }
264 let mut cursor = node.walk();
265 for child in node.children(&mut cursor) {
266 if child.kind() == "type_identifier" {
267 return node_text(child, src).trim().to_string();
268 }
269 }
270 String::new()
271}
272
273fn try_extract_macro_class(node: Node, src: &[u8]) -> Option<ClassInfo> {
276 let mut has_class_spec = false;
277 let mut cursor = node.walk();
278 for child in node.children(&mut cursor) {
279 if child.kind() == "class_specifier" || child.kind() == "struct_specifier" {
280 has_class_spec = true;
281 }
282 }
283 if !has_class_spec {
284 return None;
285 }
286 let name_node = node
288 .child_by_field_name("declarator")
289 .filter(|d| d.kind() == "identifier")?;
290 let name = node_text(name_node, src).to_string();
291 let name_col = name_node.start_position().column;
292 let name_end_col = name_node.end_position().column;
293 let body = node.child_by_field_name("body")?;
294 let start_line = node.start_position().row + 1;
295 let end_line = node.end_position().row + 1;
296 let method_count = count_methods(body);
297 let (field_names, field_types, first_field_type) = extract_field_info(body, src);
298
299 let parent_name = first_field_type;
301
302 Some(ClassInfo {
303 name,
304 start_line,
305 end_line,
306 name_col,
307 name_end_col,
308 line_count: end_line - start_line + 1,
309 method_count,
310 is_exported: true,
311 delegating_method_count: 0,
312 field_count: field_names.len(),
313 field_names,
314 field_types,
315 has_behavior: method_count > 0,
316 is_interface: false,
317 parent_name,
318 override_count: 0,
319 self_call_count: 0,
320 has_listener_field: false,
321 has_notify_method: false,
322 })
323}
324
325fn extract_function(
326 node: Node,
327 src: &[u8],
328 imports_map: &crate::type_ref::ImportsMap,
329) -> Option<FunctionInfo> {
330 let declarator = node.child_by_field_name("declarator")?;
331 let name_node = find_func_name_node(declarator)?;
332 let name = node_text(name_node, src).to_string();
333 let name_col = name_node.start_position().column;
334 let name_end_col = name_node.end_position().column;
335 let start_line = node.start_position().row + 1;
336 let end_line = node.end_position().row + 1;
337 let body = node.child_by_field_name("body");
338 let (param_count, param_types, param_names) = extract_params(declarator, src, imports_map);
339 let is_static = has_storage_class(node, src, "static");
340
341 Some(FunctionInfo {
342 name,
343 start_line,
344 end_line,
345 name_col,
346 name_end_col,
347 line_count: end_line - start_line + 1,
348 complexity: count_complexity(node),
349 body_hash: body.map(hash_ast),
350 is_exported: !is_static,
351 parameter_count: param_count,
352 parameter_types: param_types,
353 parameter_names: param_names,
354 chain_depth: body.map(max_chain_depth).unwrap_or(0),
355 switch_arms: body.map(count_case_labels).unwrap_or(0),
356 switch_arm_values: body
357 .map(|b| collect_c_arm_values(b, src))
358 .unwrap_or_default(),
359 external_refs: body
360 .map(|b| collect_external_refs_c(b, src))
361 .unwrap_or_default(),
362 is_delegating: body.map(|b| check_delegating_c(b, src)).unwrap_or(false),
363 comment_lines: count_comment_lines(node, src),
364 referenced_fields: body
365 .map(|b| collect_field_refs_c(b, src))
366 .unwrap_or_default(),
367 null_check_fields: body
368 .map(|b| collect_null_checks_c(b, src))
369 .unwrap_or_default(),
370 switch_dispatch_target: body.and_then(|b| extract_switch_target_c(b, src)),
371 optional_param_count: 0,
372 called_functions: body.map(|b| collect_calls_c(b, src)).unwrap_or_default(),
373 cognitive_complexity: body.map(cognitive_complexity_c).unwrap_or(0),
374 return_type: extract_c_return_type(node, src, imports_map),
375 })
376}
377
378fn extract_c_return_type(
383 node: Node,
384 src: &[u8],
385 imports_map: &crate::type_ref::ImportsMap,
386) -> Option<cha_core::TypeRef> {
387 let ty = node.child_by_field_name("type")?;
388 let base = node_text(ty, src).trim().to_string();
389 let is_ptr = node
390 .child_by_field_name("declarator")
391 .is_some_and(|d| d.kind() == "pointer_declarator");
392 let raw = if is_ptr { format!("{base} *") } else { base };
393 Some(crate::type_ref::resolve(raw, imports_map))
394}
395
396fn has_function_declarator(node: Node) -> bool {
400 node.child_by_field_name("declarator")
401 .is_some_and(has_function_declarator_inside)
402}
403
404fn has_function_declarator_inside(node: Node) -> bool {
405 if node.kind() == "function_declarator" {
406 return true;
407 }
408 if let Some(inner) = node.child_by_field_name("declarator") {
411 return has_function_declarator_inside(inner);
412 }
413 false
414}
415
416fn has_storage_class(node: Node, src: &[u8], keyword: &str) -> bool {
418 for i in 0..node.child_count() {
419 if let Some(child) = node.child(i)
420 && child.kind() == "storage_class_specifier"
421 && node_text(child, src) == keyword
422 {
423 return true;
424 }
425 }
426 false
427}
428
429fn find_func_name_node(declarator: Node) -> Option<Node> {
430 match declarator.kind() {
434 "identifier" | "field_identifier" | "destructor_name" | "operator_name" => {
435 return Some(declarator);
436 }
437 "qualified_identifier" => return crate::cpp::qualified_identifier_leaf(declarator),
438 _ => {}
439 }
440 let next = declarator
444 .child_by_field_name("declarator")
445 .or_else(|| first_named_child(declarator));
446 next.and_then(find_func_name_node)
447}
448
449fn first_named_child(node: Node) -> Option<Node> {
450 let mut c = node.walk();
451 node.children(&mut c).find(|n| n.is_named())
452}
453
454fn extract_params(
455 declarator: Node,
456 src: &[u8],
457 imports_map: &crate::type_ref::ImportsMap,
458) -> (usize, Vec<cha_core::TypeRef>, Vec<String>) {
459 let params = match declarator.child_by_field_name("parameters") {
460 Some(p) => p,
461 None => return (0, vec![], vec![]),
462 };
463 let mut count = 0;
464 let mut types = Vec::new();
465 let mut names = Vec::new();
466 let mut cursor = params.walk();
467 for child in params.children(&mut cursor) {
468 if child.kind() == "parameter_declaration" {
469 count += 1;
470 let base = child
471 .child_by_field_name("type")
472 .map(|t| node_text(t, src).to_string())
473 .unwrap_or_else(|| "int".into());
474 let decl = child.child_by_field_name("declarator");
475 let is_ptr = decl.is_some_and(|d| d.kind() == "pointer_declarator");
476 let raw = if is_ptr { format!("{base} *") } else { base };
477 types.push(crate::type_ref::resolve(raw, imports_map));
478 names.push(
479 decl.map(|d| crate::cpp::c_param_name(d, src))
480 .unwrap_or_default(),
481 );
482 }
483 }
484 (count, types, names)
485}
486
487fn extract_class(node: Node, src: &[u8]) -> Option<ClassInfo> {
488 let (name, name_col, name_end_col) =
489 crate::cpp::class_name_triple(node.child_by_field_name("name"), src);
490 let start_line = node.start_position().row + 1;
491 let end_line = node.end_position().row + 1;
492 let body = node.child_by_field_name("body");
493 let method_count = body.map(count_methods).unwrap_or(0);
494 let (field_names, field_types, first_field_type) =
495 body.map(|b| extract_field_info(b, src)).unwrap_or_default();
496
497 let parent_name = crate::cpp::extract_cpp_base(node, src).or(first_field_type);
503
504 Some(ClassInfo {
505 name,
506 start_line,
507 end_line,
508 name_col,
509 name_end_col,
510 line_count: end_line - start_line + 1,
511 method_count,
512 is_exported: true,
513 delegating_method_count: 0,
514 field_count: field_names.len(),
515 field_names,
516 field_types,
517 has_behavior: method_count > 0,
518 is_interface: false,
519 parent_name,
520 override_count: 0,
521 self_call_count: 0,
522 has_listener_field: false,
523 has_notify_method: false,
524 })
525}
526
527fn extract_field_info(body: Node, src: &[u8]) -> (Vec<String>, Vec<String>, Option<String>) {
528 let mut names = Vec::new();
529 let mut types = Vec::new();
530 let mut first_type = None;
531 let mut cursor = body.walk();
532 for child in body.children(&mut cursor) {
533 if child.kind() == "field_declaration" {
534 if let Some(decl) = child.child_by_field_name("declarator") {
535 names.push(node_text(decl, src).to_string());
536 }
537 let ty = child
538 .child_by_field_name("type")
539 .map(|t| node_text(t, src).to_string());
540 if first_type.is_none() {
541 first_type = ty.clone();
542 }
543 types.push(ty.unwrap_or_default());
544 }
545 }
546 (names, types, first_type)
547}
548
549fn count_methods(body: Node) -> usize {
550 let mut count = 0;
551 let mut cursor = body.walk();
552 for child in body.children(&mut cursor) {
553 if child.kind() == "function_definition" || child.kind() == "declaration" {
554 count += 1;
555 }
556 }
557 count
558}
559
560fn extract_include(node: Node, src: &[u8]) -> Option<ImportInfo> {
561 let path = node.child_by_field_name("path")?;
562 let text = node_text(path, src)
563 .trim_matches(|c| c == '"' || c == '<' || c == '>')
564 .to_string();
565 Some(ImportInfo {
566 source: text,
567 line: node.start_position().row + 1,
568 col: node.start_position().column,
569 ..Default::default()
570 })
571}
572
573fn count_complexity(node: Node) -> usize {
574 let mut c = 1usize;
575 let mut cursor = node.walk();
576 visit_all(node, &mut cursor, &mut |n| match n.kind() {
577 "if_statement"
578 | "for_statement"
579 | "while_statement"
580 | "do_statement"
581 | "case_statement"
582 | "catch_clause"
583 | "conditional_expression" => c += 1,
584 "binary_expression" => {
585 if let Some(op) = n.child_by_field_name("operator") {
586 let kind = op.kind();
587 if kind == "&&" || kind == "||" {
588 c += 1;
589 }
590 }
591 }
592 _ => {}
593 });
594 c
595}
596
597fn max_chain_depth(node: Node) -> usize {
598 let mut max = 0;
599 let mut cursor = node.walk();
600 visit_all(node, &mut cursor, &mut |n| {
601 if n.kind() == "field_expression" {
602 let d = chain_len(n);
603 if d > max {
604 max = d;
605 }
606 }
607 });
608 max
609}
610
611fn chain_len(node: Node) -> usize {
612 let mut depth = 0;
613 let mut current = node;
614 while current.kind() == "field_expression" || current.kind() == "call_expression" {
615 if current.kind() == "field_expression" {
616 depth += 1;
617 }
618 match current.child(0) {
619 Some(c) => current = c,
620 None => break,
621 }
622 }
623 depth
624}
625
626fn collect_c_arm_values(body: Node, src: &[u8]) -> Vec<cha_core::ArmValue> {
627 let mut out = Vec::new();
628 crate::switch_arms::walk_arms(body, src, &mut out, &|n| n.kind() == "case_statement");
629 out
630}
631
632fn count_case_labels(node: Node) -> usize {
633 let mut count = 0;
634 let mut cursor = node.walk();
635 visit_all(node, &mut cursor, &mut |n| {
636 if n.kind() == "case_statement" {
637 count += 1;
638 }
639 });
640 count
641}
642
643fn cognitive_complexity_c(node: tree_sitter::Node) -> usize {
644 let mut score = 0;
645 cc_walk_c(node, 0, &mut score);
646 score
647}
648
649fn cc_walk_c(node: tree_sitter::Node, nesting: usize, score: &mut usize) {
650 match node.kind() {
651 "if_statement" => {
652 *score += 1 + nesting;
653 cc_children_c(node, nesting + 1, score);
654 return;
655 }
656 "for_statement" | "while_statement" | "do_statement" => {
657 *score += 1 + nesting;
658 cc_children_c(node, nesting + 1, score);
659 return;
660 }
661 "switch_statement" => {
662 *score += 1 + nesting;
663 cc_children_c(node, nesting + 1, score);
664 return;
665 }
666 "else_clause" => {
667 *score += 1;
668 }
669 "binary_expression" => {
670 if let Some(op) = node.child_by_field_name("operator")
671 && (op.kind() == "&&" || op.kind() == "||")
672 {
673 *score += 1;
674 }
675 }
676 "catch_clause" => {
677 *score += 1 + nesting;
678 cc_children_c(node, nesting + 1, score);
679 return;
680 }
681 _ => {}
682 }
683 cc_children_c(node, nesting, score);
684}
685
686fn cc_children_c(node: tree_sitter::Node, nesting: usize, score: &mut usize) {
687 let mut cursor = node.walk();
688 for child in node.children(&mut cursor) {
689 cc_walk_c(child, nesting, score);
690 }
691}
692
693fn collect_external_refs_c(body: Node, src: &[u8]) -> Vec<String> {
694 let mut refs = Vec::new();
695 let mut cursor = body.walk();
696 visit_all(body, &mut cursor, &mut |n| {
697 if n.kind() == "field_expression"
698 && let Some(obj) = n.child(0)
699 && obj.kind() == "identifier"
700 {
701 let name = node_text(obj, src).to_string();
702 if !refs.contains(&name) {
703 refs.push(name);
704 }
705 }
706 });
707 refs
708}
709
710fn check_delegating_c(body: Node, src: &[u8]) -> bool {
711 let mut cursor = body.walk();
712 let stmts: Vec<Node> = body
713 .children(&mut cursor)
714 .filter(|n| n.kind() != "{" && n.kind() != "}" && !n.kind().contains("comment"))
715 .collect();
716 if stmts.len() != 1 {
717 return false;
718 }
719 let stmt = stmts[0];
720 let call = match stmt.kind() {
721 "return_statement" => stmt.child(1).filter(|c| c.kind() == "call_expression"),
722 "expression_statement" => stmt.child(0).filter(|c| c.kind() == "call_expression"),
723 _ => None,
724 };
725 call.and_then(|c| c.child(0))
726 .is_some_and(|f| node_text(f, src).contains('.') || node_text(f, src).contains("->"))
727}
728
729fn collect_field_refs_c(body: Node, src: &[u8]) -> Vec<String> {
730 let mut refs = Vec::new();
731 let mut cursor = body.walk();
732 visit_all(body, &mut cursor, &mut |n| {
733 if n.kind() == "field_expression"
734 && let Some(field) = n.child_by_field_name("field")
735 {
736 let name = node_text(field, src).to_string();
737 if !refs.contains(&name) {
738 refs.push(name);
739 }
740 }
741 });
742 refs
743}
744
745fn collect_null_checks_c(body: Node, src: &[u8]) -> Vec<String> {
746 let mut fields = Vec::new();
747 let mut cursor = body.walk();
748 visit_all(body, &mut cursor, &mut |n| {
749 if n.kind() == "binary_expression" {
750 let text = node_text(n, src);
751 if (text.contains("NULL") || text.contains("nullptr"))
752 && let Some(left) = n.child(0)
753 {
754 let name = node_text(left, src).to_string();
755 if !fields.contains(&name) {
756 fields.push(name);
757 }
758 }
759 }
760 });
761 fields
762}
763
764fn extract_switch_target_c(body: Node, src: &[u8]) -> Option<String> {
765 let mut cursor = body.walk();
766 let mut target = None;
767 visit_all(body, &mut cursor, &mut |n| {
768 if n.kind() == "switch_statement"
769 && target.is_none()
770 && let Some(cond) = n.child_by_field_name("condition")
771 {
772 target = Some(node_text(cond, src).trim_matches(['(', ')']).to_string());
773 }
774 });
775 target
776}
777
778fn collect_calls_c(body: Node, src: &[u8]) -> Vec<String> {
779 let mut calls = Vec::new();
780 let mut cursor = body.walk();
781 visit_all(body, &mut cursor, &mut |n| {
782 if n.kind() == "call_expression"
783 && let Some(func) = n.child(0)
784 {
785 let name = node_text(func, src).to_string();
786 if !calls.contains(&name) {
787 calls.push(name);
788 }
789 }
790 });
791 calls
792}
793
794fn count_comment_lines(node: Node, src: &[u8]) -> usize {
795 let mut count = 0;
796 let mut cursor = node.walk();
797 visit_all(node, &mut cursor, &mut |n| {
798 if n.kind() == "comment" {
799 count += node_text(n, src).lines().count();
800 }
801 });
802 count
803}
804
805fn hash_ast(node: Node) -> u64 {
806 let mut hasher = DefaultHasher::new();
807 hash_node(node, &mut hasher);
808 hasher.finish()
809}
810
811fn hash_node(node: Node, hasher: &mut DefaultHasher) {
812 node.kind().hash(hasher);
813 let mut cursor = node.walk();
814 for child in node.children(&mut cursor) {
815 hash_node(child, hasher);
816 }
817}
818
819fn node_text<'a>(node: Node, src: &'a [u8]) -> &'a str {
820 node.utf8_text(src).unwrap_or("")
821}
822
823fn collect_comments(root: Node, src: &[u8]) -> Vec<cha_core::CommentInfo> {
824 let mut comments = Vec::new();
825 let mut cursor = root.walk();
826 visit_all(root, &mut cursor, &mut |n| {
827 if n.kind().contains("comment") {
828 comments.push(cha_core::CommentInfo {
829 text: node_text(n, src).to_string(),
830 line: n.start_position().row + 1,
831 });
832 }
833 });
834 comments
835}
836
837fn visit_all<F: FnMut(Node)>(node: Node, cursor: &mut tree_sitter::TreeCursor, f: &mut F) {
838 f(node);
839 if cursor.goto_first_child() {
840 loop {
841 let child_node = cursor.node();
842 let mut child_cursor = child_node.walk();
843 visit_all(child_node, &mut child_cursor, f);
844 if !cursor.goto_next_sibling() {
845 break;
846 }
847 }
848 cursor.goto_parent();
849 }
850}