1mod declaration;
4mod diagnostics;
5mod type_parse;
6
7use declaration::{lower_declaration, lower_record};
8pub(super) use type_parse::parse_type;
9
10use tree_sitter::{Node, Parser};
11
12use crate::{
13 Decl, FunctionQualifiers, Identifier, IdentifierPath, Language, Linkage, MethodKind,
14 NamedTypeTag, ObjectiveCForwardKind, ObjectiveCMethod, Parameter, ParameterState, RecordKind,
15 StorageClass, TranslationUnit,
16};
17
18#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
20pub struct SourceSpan {
21 pub start: usize,
23 pub end: usize,
25 pub line: usize,
27 pub column: usize,
29}
30
31#[derive(Debug, Clone, PartialEq, Eq)]
33pub struct SyntaxIssue {
34 pub kind: String,
36 pub span: SourceSpan,
38}
39
40#[derive(Debug, thiserror::Error)]
42pub enum ParseError {
43 #[error("failed to initialize {0:?} header grammar")]
45 Grammar(Language),
46 #[error("header parser did not produce a syntax tree")]
48 NoTree,
49 #[error("header contains syntax errors: {}", diagnostics::format_syntax_issues(.0))]
51 Syntax(Vec<SyntaxIssue>),
52 #[error("unsupported header construct `{kind}` at byte {span_start}")]
54 Unsupported {
55 kind: String,
57 span_start: usize,
59 },
60 #[error("invalid declaration: {0}")]
62 InvalidDeclaration(String),
63}
64
65pub trait HeaderParser {
67 fn parse(&self, language: Language, source: &str) -> Result<TranslationUnit, ParseError>;
69}
70
71#[derive(Debug, Default, Clone, Copy)]
73pub struct TreeSitterHeaderParser;
74
75impl HeaderParser for TreeSitterHeaderParser {
76 fn parse(&self, language: Language, source: &str) -> Result<TranslationUnit, ParseError> {
77 let mut parser = Parser::new();
78 let grammar = match language {
79 Language::C => tree_sitter_c::LANGUAGE,
80 Language::Cpp => tree_sitter_cpp::LANGUAGE,
81 Language::ObjectiveC => tree_sitter_objc::LANGUAGE,
82 };
83 parser
84 .set_language(&grammar.into())
85 .map_err(|_| ParseError::Grammar(language))?;
86 let tree = parser.parse(source, None).ok_or(ParseError::NoTree)?;
87 let root = tree.root_node();
88 if root.has_error() {
89 let mut issues = Vec::new();
90 collect_syntax_issues(root, &mut issues);
91 return Err(ParseError::Syntax(issues));
92 }
93
94 let mut declarations = Vec::new();
95 let mut declaration_spans = Vec::new();
96 lower_children(
97 root,
98 source,
99 language,
100 &mut declarations,
101 &mut declaration_spans,
102 )?;
103 Ok(TranslationUnit {
104 language,
105 declarations,
106 declaration_spans,
107 })
108 }
109}
110
111fn collect_syntax_issues(node: Node<'_>, issues: &mut Vec<SyntaxIssue>) {
112 if node.is_error() || node.is_missing() {
113 let point = node.start_position();
114 issues.push(SyntaxIssue {
115 kind: if node.is_missing() {
116 format!("missing {}", node.kind())
117 } else {
118 node.kind().to_owned()
119 },
120 span: SourceSpan {
121 start: node.start_byte(),
122 end: node.end_byte(),
123 line: point.row + 1,
124 column: point.column + 1,
125 },
126 });
127 return;
128 }
129 let mut cursor = node.walk();
130 for child in node.children(&mut cursor) {
131 collect_syntax_issues(child, issues);
132 }
133}
134
135fn lower_children(
136 node: Node<'_>,
137 source: &str,
138 language: Language,
139 declarations: &mut Vec<Decl>,
140 declaration_spans: &mut Vec<SourceSpan>,
141) -> Result<(), ParseError> {
142 let mut cursor = node.walk();
143 for child in node.named_children(&mut cursor) {
144 let text = child
145 .utf8_text(source.as_bytes())
146 .map_err(|_| ParseError::InvalidDeclaration("header is not valid UTF-8".to_owned()))?;
147 match child.kind() {
148 "comment"
149 | "preproc_include"
150 | "preproc_def"
151 | "preproc_function_def"
152 | "preproc_call"
153 | "preproc_if"
154 | "preproc_ifdef" => {}
155 "declaration" | "type_definition" | "alias_declaration" => {
156 extend_declarations(
157 declarations,
158 declaration_spans,
159 lower_declaration(text, language)?,
160 source_span(child),
161 );
162 }
163 "struct_specifier" | "union_specifier" | "class_specifier" | "enum_specifier" => {
164 declarations.push(lower_record(text, language)?);
165 declaration_spans.push(source_span(child));
166 }
167 "namespace_definition" => {
168 let body = child
172 .child_by_field_name("body")
173 .or_else(|| named_child_of_kind(child, "declaration_list"))
174 .ok_or_else(|| ParseError::InvalidDeclaration(text.to_owned()))?;
175 lower_children(body, source, language, declarations, declaration_spans)?;
176 }
177 "linkage_specification" => {
178 let mut nested = child.walk();
180 for declaration in child
181 .named_children(&mut nested)
182 .filter(|node| matches!(node.kind(), "declaration" | "declaration_list"))
183 {
184 if declaration.kind() == "declaration_list" {
185 lower_children(
186 declaration,
187 source,
188 language,
189 declarations,
190 declaration_spans,
191 )?;
192 } else {
193 let text = declaration.utf8_text(source.as_bytes()).map_err(|_| {
194 ParseError::InvalidDeclaration("header is not valid UTF-8".to_owned())
195 })?;
196 extend_declarations(
197 declarations,
198 declaration_spans,
199 lower_declaration(text, language)?,
200 source_span(declaration),
201 );
202 }
203 }
204 }
205 "declaration_list" => {
206 lower_children(child, source, language, declarations, declaration_spans)?
207 }
208 "class_interface" => {
209 declarations.push(lower_objc_interface(text)?);
210 declaration_spans.push(source_span(child));
211 }
212 "protocol_declaration" | "qualified_protocol_interface_declaration" => {
213 declarations.push(lower_objc_protocol(text)?);
214 declaration_spans.push(source_span(child));
215 }
216 "class_forward_declaration" | "class_declaration" => {
217 declarations.push(lower_objc_forward(text, ObjectiveCForwardKind::Class)?);
218 declaration_spans.push(source_span(child));
219 }
220 "protocol_forward_declaration" | "protocol_forward_declaration_list" => {
221 declarations.push(lower_objc_forward(text, ObjectiveCForwardKind::Protocol)?);
222 declaration_spans.push(source_span(child));
223 }
224 ";" | "preproc_directive" => {}
226 kind => {
227 return Err(ParseError::Unsupported {
228 kind: kind.to_owned(),
229 span_start: child.start_byte(),
230 });
231 }
232 }
233 }
234 Ok(())
235}
236
237fn extend_declarations(
238 declarations: &mut Vec<Decl>,
239 spans: &mut Vec<SourceSpan>,
240 lowered: Vec<Decl>,
241 span: SourceSpan,
242) {
243 spans.extend(std::iter::repeat_n(span, lowered.len()));
244 declarations.extend(lowered);
245}
246
247fn source_span(node: Node<'_>) -> SourceSpan {
248 let point = node.start_position();
249 SourceSpan {
250 start: node.start_byte(),
251 end: node.end_byte(),
252 line: point.row + 1,
253 column: point.column + 1,
254 }
255}
256
257fn named_child_of_kind<'tree>(node: Node<'tree>, kind: &str) -> Option<Node<'tree>> {
258 let mut cursor = node.walk();
259 node.named_children(&mut cursor)
260 .find(|child| child.kind() == kind)
261}
262
263fn lower_objc_interface(text: &str) -> Result<Decl, ParseError> {
264 let header = text
265 .lines()
266 .next()
267 .ok_or_else(|| ParseError::InvalidDeclaration(text.to_owned()))?
268 .trim()
269 .trim_start_matches("@interface")
270 .trim();
271 let head = header.split('{').next().unwrap_or(header).trim();
272 let (before_protocols, protocols) = parse_objc_protocols(head)?;
273 if let Some(open) = before_protocols.find('(') {
274 let close = before_protocols[open + 1..]
275 .find(')')
276 .map(|offset| open + 1 + offset)
277 .ok_or_else(|| ParseError::InvalidDeclaration(text.to_owned()))?;
278 let extended_class = parse_identifier(before_protocols[..open].trim())?;
279 let name = parse_identifier(before_protocols[open + 1..close].trim())?;
280 let (methods, properties) = parse_objc_members(text)?;
281 return Ok(Decl::ObjectiveCCategory {
282 name,
283 extended_class,
284 protocols,
285 methods,
286 properties,
287 });
288 }
289 let (name, superclass) = if let Some((name, superclass)) = before_protocols.split_once(':') {
290 (
291 parse_identifier(name.trim())?,
292 Some(parse_identifier(superclass.trim())?),
293 )
294 } else {
295 (parse_identifier(before_protocols.trim())?, None)
296 };
297 let (methods, properties) = parse_objc_members(text)?;
298 Ok(Decl::ObjectiveCInterface {
299 name,
300 superclass,
301 protocols,
302 ivars: parse_objc_ivars(text)?,
303 methods,
304 properties,
305 })
306}
307
308fn lower_objc_protocol(text: &str) -> Result<Decl, ParseError> {
309 let header = text
310 .lines()
311 .next()
312 .ok_or_else(|| ParseError::InvalidDeclaration(text.to_owned()))?
313 .trim()
314 .trim_start_matches("@protocol")
315 .trim();
316 let (name, protocols) = parse_objc_protocols(header)?;
317 let (methods, properties) = parse_objc_members(text)?;
318 Ok(Decl::ObjectiveCProtocol {
319 name: parse_identifier(name.trim())?,
320 protocols,
321 methods,
322 properties,
323 })
324}
325
326fn lower_objc_forward(text: &str, kind: ObjectiveCForwardKind) -> Result<Decl, ParseError> {
327 let keyword = match kind {
328 ObjectiveCForwardKind::Class => "@class",
329 ObjectiveCForwardKind::Protocol => "@protocol",
330 };
331 let names = text
332 .trim()
333 .trim_start_matches(keyword)
334 .trim_end_matches(';')
335 .split(',')
336 .map(str::trim)
337 .map(parse_identifier)
338 .collect::<Result<Vec<_>, _>>()?;
339 if names.is_empty() {
340 return Err(ParseError::InvalidDeclaration(text.to_owned()));
341 }
342 Ok(Decl::ObjectiveCForward { kind, names })
343}
344
345fn parse_objc_protocols(text: &str) -> Result<(&str, Vec<Identifier>), ParseError> {
346 let Some(open) = text.find('<') else {
347 return Ok((text, Vec::new()));
348 };
349 let close = text[open + 1..]
350 .find('>')
351 .map(|offset| open + 1 + offset)
352 .ok_or_else(|| ParseError::InvalidDeclaration(text.to_owned()))?;
353 let protocols = text[open + 1..close]
354 .split(',')
355 .map(str::trim)
356 .map(parse_identifier)
357 .collect::<Result<Vec<_>, _>>()?;
358 Ok((text[..open].trim(), protocols))
359}
360
361fn parse_objc_members(
362 text: &str,
363) -> Result<(Vec<ObjectiveCMethod>, Vec<crate::ObjectiveCProperty>), ParseError> {
364 let mut without_ivars = text.to_owned();
365 if let Some(open) = without_ivars.find('{')
366 && let Some(close) = matching_delimiter(&without_ivars, open, '{', '}')
367 {
368 without_ivars.replace_range(open..=close, "");
369 }
370 let mut body = without_ivars.lines().skip(1).collect::<Vec<_>>().join("\n");
371 body = body.replace("@end", "");
372 let mut required = None;
373 let mut methods = Vec::new();
374 let mut properties = Vec::new();
375 for raw in split_top_level(&body, ';') {
376 let mut value = raw.trim();
377 for (directive, state) in [("@required", Some(true)), ("@optional", Some(false))] {
378 if let Some(rest) = value.strip_prefix(directive) {
379 required = state;
380 value = rest.trim();
381 }
382 }
383 if value.starts_with('-') || value.starts_with('+') {
384 let mut method = parse_objc_method(value)?;
385 method.required = required;
386 methods.push(method);
387 } else if value.starts_with("@property") {
388 properties.push(parse_objc_property(value)?);
389 }
390 }
391 Ok((methods, properties))
392}
393
394fn parse_objc_ivars(text: &str) -> Result<Vec<crate::ObjectiveCIvar>, ParseError> {
395 let Some(open) = text.find('{') else {
396 return Ok(Vec::new());
397 };
398 let close = matching_delimiter(text, open, '{', '}')
399 .ok_or_else(|| ParseError::InvalidDeclaration(text.to_owned()))?;
400 let mut access = crate::ObjectiveCAccess::Protected;
401 let mut ivars = Vec::new();
402 for raw in split_top_level(&text[open + 1..close], ';') {
403 let mut value = raw.trim();
404 for (directive, next) in [
405 ("@public", crate::ObjectiveCAccess::Public),
406 ("@protected", crate::ObjectiveCAccess::Protected),
407 ("@private", crate::ObjectiveCAccess::Private),
408 ("@package", crate::ObjectiveCAccess::Package),
409 ] {
410 if let Some(rest) = value.strip_prefix(directive) {
411 access = next;
412 value = rest.trim();
413 }
414 }
415 if value.is_empty() {
416 continue;
417 }
418 let (ty, name) = split_type_and_name(value)?;
419 ivars.push(crate::ObjectiveCIvar {
420 name: parse_identifier(name.trim_start_matches('*').trim())?,
421 ty: parse_type(ty, Language::ObjectiveC)?,
422 access,
423 });
424 }
425 Ok(ivars)
426}
427
428fn parse_objc_property(text: &str) -> Result<crate::ObjectiveCProperty, ParseError> {
429 let mut value = text.trim_start_matches("@property").trim();
430 let mut attributes = Vec::new();
431 if value.starts_with('(') {
432 let close = matching_delimiter(value, 0, '(', ')')
433 .ok_or_else(|| ParseError::InvalidDeclaration(text.to_owned()))?;
434 attributes = value[1..close]
435 .split(',')
436 .map(str::trim)
437 .map(parse_objc_property_attribute)
438 .collect::<Result<Vec<_>, _>>()?;
439 value = value[close + 1..].trim();
440 }
441 let (ty, name) = split_type_and_name(value)?;
442 Ok(crate::ObjectiveCProperty {
443 name: parse_identifier(name.trim_start_matches('*').trim())?,
444 ty: parse_type(ty, Language::ObjectiveC)?,
445 attributes,
446 })
447}
448
449fn parse_objc_property_attribute(
450 value: &str,
451) -> Result<crate::ObjectiveCPropertyAttribute, ParseError> {
452 use crate::ObjectiveCPropertyAttribute as Attribute;
453 match value {
454 "readonly" => Ok(Attribute::Readonly),
455 "readwrite" => Ok(Attribute::Readwrite),
456 "copy" => Ok(Attribute::Copy),
457 "retain" => Ok(Attribute::Retain),
458 "strong" => Ok(Attribute::Strong),
459 "weak" => Ok(Attribute::Weak),
460 "assign" => Ok(Attribute::Assign),
461 "atomic" => Ok(Attribute::Atomic),
462 "nonatomic" => Ok(Attribute::Nonatomic),
463 "dynamic" => Ok(Attribute::Dynamic),
464 "class" => Ok(Attribute::Class),
465 _ => Err(ParseError::InvalidDeclaration(format!(
466 "unknown Objective-C property attribute `{value}`"
467 ))),
468 }
469}
470
471fn parse_objc_method(text: &str) -> Result<ObjectiveCMethod, ParseError> {
472 let kind = if text.starts_with('+') {
473 MethodKind::Class
474 } else {
475 MethodKind::Instance
476 };
477 let rest = text[1..].trim();
478 let return_open = rest
479 .find('(')
480 .ok_or_else(|| ParseError::InvalidDeclaration(text.to_owned()))?;
481 let return_close = matching_delimiter(rest, return_open, '(', ')')
482 .ok_or_else(|| ParseError::InvalidDeclaration(text.to_owned()))?;
483 let return_type = parse_type(&rest[return_open + 1..return_close], Language::ObjectiveC)?;
484 let tail = rest[return_close + 1..].trim();
485 if !tail.contains(':') {
486 return Ok(ObjectiveCMethod {
487 kind,
488 selector: tail.to_owned(),
489 return_type,
490 parameters: Vec::new(),
491 required: None,
492 });
493 }
494 let mut selector = String::new();
495 let mut parameters = Vec::new();
496 let mut remaining = tail;
497 let mut index = 0usize;
498 while let Some(colon) = remaining.find(':') {
499 let piece = remaining[..colon]
500 .split_whitespace()
501 .last()
502 .unwrap_or_default();
503 selector.push_str(piece);
504 selector.push(':');
505 remaining = remaining[colon + 1..].trim_start();
506 let open = remaining
507 .find('(')
508 .ok_or_else(|| ParseError::InvalidDeclaration(text.to_owned()))?;
509 let close = matching_delimiter(remaining, open, '(', ')')
510 .ok_or_else(|| ParseError::InvalidDeclaration(text.to_owned()))?;
511 let ty = parse_type(&remaining[open + 1..close], Language::ObjectiveC)?;
512 remaining = remaining[close + 1..].trim_start();
513 let end = remaining
514 .find(char::is_whitespace)
515 .unwrap_or(remaining.len());
516 let candidate = &remaining[..end];
517 let name = Identifier::new(candidate).unwrap_or_else(|| {
518 Identifier::new(format!("arg{index}")).expect("generated identifier is valid")
519 });
520 index += 1;
521 parameters.push(Parameter { name, ty });
522 remaining = remaining[end..].trim_start();
523 }
524 Ok(ObjectiveCMethod {
525 kind,
526 selector,
527 return_type,
528 parameters,
529 required: None,
530 })
531}
532
533pub(super) fn parse_parameters(
534 text: &str,
535 language: Language,
536) -> Result<(Vec<Parameter>, bool, ParameterState), ParseError> {
537 let text = text.trim();
538 if text.is_empty() {
539 return Ok((Vec::new(), false, ParameterState::Unspecified));
540 }
541 if text == "void" {
542 return Ok((Vec::new(), false, ParameterState::Known));
543 }
544 let mut parameters = Vec::new();
545 let mut variadic = false;
546 for (index, parameter) in split_top_level(text, ',').into_iter().enumerate() {
547 let parameter = parameter.trim();
548 if parameter == "..." {
549 variadic = true;
550 continue;
551 }
552 let (ty, name) = split_type_and_name(parameter).unwrap_or((parameter, ""));
553 let name = Identifier::new(name.trim_start_matches('*').trim()).unwrap_or_else(|| {
554 Identifier::new(format!("arg{index}")).expect("generated identifier is valid")
555 });
556 parameters.push(Parameter {
557 name,
558 ty: parse_type(ty, language)?,
559 });
560 }
561 Ok((parameters, variadic, ParameterState::Known))
562}
563
564pub(super) fn split_type_and_declarators(text: &str) -> Result<(&str, Vec<&str>), ParseError> {
565 let parts = split_top_level(text, ',');
566 let first = parts
567 .first()
568 .copied()
569 .ok_or_else(|| ParseError::InvalidDeclaration(text.to_owned()))?;
570 let (ty, first_name) = split_type_and_name(first)?;
571 let mut names = vec![first_name];
572 names.extend(parts.into_iter().skip(1));
573 Ok((ty, names))
574}
575
576pub(super) fn split_type_and_name(text: &str) -> Result<(&str, &str), ParseError> {
577 let text = text.trim();
578 let end = text
579 .char_indices()
580 .rev()
581 .find(|(_, ch)| ch.is_ascii_alphanumeric() || *ch == '_')
582 .map(|(index, ch)| index + ch.len_utf8())
583 .ok_or_else(|| ParseError::InvalidDeclaration(text.to_owned()))?;
584 let start = text[..end]
585 .char_indices()
586 .rev()
587 .take_while(|(_, ch)| ch.is_ascii_alphanumeric() || *ch == '_')
588 .last()
589 .map(|(index, _)| index)
590 .unwrap_or(0);
591 let name = &text[start..end];
592 let ty = text[..start].trim();
593 if ty.is_empty() || Identifier::new(name).is_none() {
594 return Err(ParseError::InvalidDeclaration(text.to_owned()));
595 }
596 Ok((ty, name))
597}
598
599pub(super) fn parse_identifier(text: &str) -> Result<Identifier, ParseError> {
600 Identifier::new(text.trim())
601 .ok_or_else(|| ParseError::InvalidDeclaration(format!("invalid identifier `{text}`")))
602}
603
604pub(super) fn parse_path(text: &str) -> Result<IdentifierPath, ParseError> {
605 IdentifierPath::parse(text)
606 .ok_or_else(|| ParseError::InvalidDeclaration(format!("invalid name `{text}`")))
607}
608
609pub(super) fn parse_storage(text: &str) -> StorageClass {
610 if text.split_whitespace().any(|word| word == "extern") {
611 StorageClass::Extern
612 } else if text.split_whitespace().any(|word| word == "static") {
613 StorageClass::Static
614 } else if text
615 .split_whitespace()
616 .any(|word| matches!(word, "thread_local" | "_Thread_local" | "__thread"))
617 {
618 StorageClass::ThreadLocal
619 } else {
620 StorageClass::None
621 }
622}
623
624pub(super) fn parse_function_qualifiers(text: &str) -> FunctionQualifiers {
625 FunctionQualifiers {
626 is_const: text.split_whitespace().any(|word| word == "const"),
627 is_volatile: text.split_whitespace().any(|word| word == "volatile"),
628 reference: if text.contains("&&") {
629 Some(crate::ReferenceKind::Rvalue)
630 } else if text.contains('&') {
631 Some(crate::ReferenceKind::Lvalue)
632 } else {
633 None
634 },
635 noexcept: text.contains("noexcept").then_some(true),
636 }
637}
638
639pub(super) fn linkage(language: Language) -> Linkage {
640 match language {
641 Language::C => Linkage::C,
642 Language::Cpp => Linkage::Cpp,
643 Language::ObjectiveC => Linkage::ObjectiveC,
644 }
645}
646
647pub(super) fn record_tag(kind: RecordKind) -> NamedTypeTag {
648 match kind {
649 RecordKind::Struct => NamedTypeTag::Struct,
650 RecordKind::Union => NamedTypeTag::Union,
651 RecordKind::Class => NamedTypeTag::Class,
652 RecordKind::Enum => NamedTypeTag::Enum,
653 }
654}
655
656pub(super) fn starts_with_record(text: &str) -> bool {
657 ["struct ", "union ", "class ", "enum "]
658 .into_iter()
659 .any(|prefix| text.starts_with(prefix))
660}
661
662pub(super) fn contains_record_body(text: &str) -> bool {
663 text.contains('{')
664 && ["struct", "union", "class", "enum"]
665 .iter()
666 .any(|kind| text.contains(kind))
667}
668
669pub(super) fn strip_attributes(mut text: &str) -> &str {
670 for prefix in ["extern ", "static ", "inline ", "__inline ", "__inline__ "] {
671 if let Some(rest) = text.strip_prefix(prefix) {
672 text = rest.trim_start();
673 }
674 }
675 text
676}
677
678pub(super) fn find_top_level(text: &str, needle: char) -> Option<usize> {
679 let mut angles = 0usize;
680 for (index, ch) in text.char_indices() {
681 match ch {
682 '<' => angles += 1,
683 '>' => angles = angles.saturating_sub(1),
684 _ if ch == needle && angles == 0 => return Some(index),
685 _ => {}
686 }
687 }
688 None
689}
690
691pub(super) fn matching_delimiter(
692 text: &str,
693 open: usize,
694 left: char,
695 right: char,
696) -> Option<usize> {
697 let mut depth = 0usize;
698 for (offset, ch) in text[open..].char_indices() {
699 if ch == left {
700 depth += 1;
701 } else if ch == right {
702 depth = depth.checked_sub(1)?;
703 if depth == 0 {
704 return Some(open + offset);
705 }
706 }
707 }
708 None
709}
710
711pub(super) fn split_top_level(text: &str, separator: char) -> Vec<&str> {
712 let mut result = Vec::new();
713 let mut start = 0usize;
714 let mut round = 0usize;
715 let mut angle = 0usize;
716 let mut square = 0usize;
717 let mut brace = 0usize;
718 for (index, ch) in text.char_indices() {
719 match ch {
720 '(' => round += 1,
721 ')' => round = round.saturating_sub(1),
722 '<' => angle += 1,
723 '>' => angle = angle.saturating_sub(1),
724 '[' => square += 1,
725 ']' => square = square.saturating_sub(1),
726 '{' => brace += 1,
727 '}' => brace = brace.saturating_sub(1),
728 _ => {}
729 }
730 if ch == separator && round == 0 && angle == 0 && square == 0 && brace == 0 {
731 result.push(&text[start..index]);
732 start = index + ch.len_utf8();
733 }
734 }
735 result.push(&text[start..]);
736 result
737}
738
739#[cfg(test)]
740mod tests {
741 use super::*;
742 use crate::Type;
743
744 #[test]
745 fn parses_c_function_and_record() {
746 let unit = TreeSitterHeaderParser
747 .parse(
748 Language::C,
749 "struct Point { int x; int y; };\nextern int distance(struct Point *point);",
750 )
751 .unwrap();
752 assert_eq!(unit.declarations.len(), 2);
753 assert!(matches!(unit.declarations[0], Decl::Record { .. }));
754 assert!(matches!(unit.declarations[1], Decl::Function { .. }));
755 }
756
757 #[test]
758 fn parses_cpp_alias_and_template_type() {
759 let unit = TreeSitterHeaderParser
760 .parse(
761 Language::Cpp,
762 "using Names = std::vector<int>;\nNames names();",
763 )
764 .unwrap();
765 assert_eq!(unit.declarations.len(), 2);
766 }
767
768 #[test]
769 fn rejects_syntax_error() {
770 let error = TreeSitterHeaderParser
771 .parse(Language::C, "int broken(;")
772 .unwrap_err();
773 assert!(matches!(error, ParseError::Syntax(_)));
774 }
775
776 #[test]
777 fn parses_objective_c_interface() {
778 let unit = TreeSitterHeaderParser
779 .parse(
780 Language::ObjectiveC,
781 "@interface Widget : NSObject\n- (int)value;\n@end",
782 )
783 .unwrap();
784 assert!(matches!(
785 unit.declarations.as_slice(),
786 [Decl::ObjectiveCInterface { .. }]
787 ));
788 }
789
790 #[test]
791 fn protocol_qualified_class_type_is_an_object_not_a_template() {
792 let unit = TreeSitterHeaderParser
797 .parse(
798 Language::ObjectiveC,
799 "@protocol Proto;\n@class NSObject;\n@interface Widget : NSObject\n{\n@protected\n NSObject<Proto> * _delegate;\n}\n@end",
800 )
801 .unwrap();
802 let [_, _, Decl::ObjectiveCInterface { ivars, .. }] = unit.declarations.as_slice() else {
803 panic!("expected an interface: {:?}", unit.declarations);
804 };
805 let Type::ObjectiveCObject {
806 name, protocols, ..
807 } = &ivars[0].ty
808 else {
809 panic!("expected an Objective-C object type: {:?}", ivars[0].ty);
810 };
811 assert_eq!(
812 name.as_ref().map(ToString::to_string).as_deref(),
813 Some("NSObject")
814 );
815 assert_eq!(
816 protocols
817 .iter()
818 .map(ToString::to_string)
819 .collect::<Vec<_>>(),
820 vec!["Proto".to_owned()]
821 );
822 }
823
824 #[test]
825 fn protocol_qualified_object_types_pass_semantic_validation() {
826 for source in [
827 "@protocol P;\n@class NSObject;\n@interface X : NSObject\n@property (readwrite, weak, atomic) NSObject<P> * d;\n@end\n",
828 "@protocol P;\n@class NSObject;\n@interface X : NSObject\n@property (readwrite, strong, atomic) id<P> d;\n@end\n",
829 ] {
830 let unit = TreeSitterHeaderParser
831 .parse(Language::ObjectiveC, source)
832 .expect("parses");
833 let validation =
834 crate::validate(&unit, crate::ValidationLimits::default()).expect("validates");
835 assert!(
836 validation.semantic_valid,
837 "expected semantic validity for {source:?}: {:?}",
838 validation.diagnostics
839 );
840 }
841 }
842
843 #[test]
844 fn plain_object_pointers_remain_pointers() {
845 let unit = TreeSitterHeaderParser
846 .parse(
847 Language::ObjectiveC,
848 "@class NSString;\n@interface Widget\n{\n@protected\n NSString * _name;\n}\n@end",
849 )
850 .unwrap();
851 let [_, Decl::ObjectiveCInterface { ivars, .. }] = unit.declarations.as_slice() else {
852 panic!("expected an interface: {:?}", unit.declarations);
853 };
854 assert!(
855 matches!(ivars[0].ty, Type::Pointer { .. }),
856 "unqualified class pointers keep their pointer shape: {:?}",
857 ivars[0].ty
858 );
859 }
860}