1use rowan::{Checkpoint, TextRange};
7
8use super::core::Parser;
9
10use super::cst::token_sets::{
11 ALT_RECOVERY, EXPR_FIRST, QUANTIFIERS, SEPARATORS, SEQ_RECOVERY, TREE_RECOVERY,
12};
13use super::cst::{SyntaxKind, TokenSet};
14use super::lexer::token_text;
15use crate::diagnostics::DiagnosticKind;
16
17impl Parser<'_> {
18 pub fn parse_root(&mut self) {
19 self.start_node(SyntaxKind::Root);
20
21 let mut unnamed_def_spans: Vec<TextRange> = Vec::new();
22
23 while !self.has_fatal_error() && (self.peek() != SyntaxKind::Error || !self.eof()) {
24 if self.peek() == SyntaxKind::Id && self.peek_nth(1) == SyntaxKind::Equals {
26 self.parse_def();
27 } else {
28 let start = self.current_span().start();
29 self.start_node(SyntaxKind::Def);
30 let success = self.parse_expr_or_error();
31 if !success {
32 self.synchronize_to_def_start();
33 }
34 self.finish_node();
35 if success {
36 let end = self.last_non_trivia_end().unwrap_or(start);
37 unnamed_def_spans.push(TextRange::new(start, end));
38 }
39 }
40 }
41
42 if unnamed_def_spans.len() > 1 {
43 for span in &unnamed_def_spans[..unnamed_def_spans.len() - 1] {
44 let def_text = &self.source[usize::from(span.start())..usize::from(span.end())];
45 self.diagnostics
46 .report(DiagnosticKind::UnnamedDefNotLast, *span)
47 .message(format!("give it a name like `Name = {}`", def_text.trim()))
48 .emit();
49 }
50 }
51
52 self.eat_trivia();
53 self.finish_node();
54 }
55
56 fn parse_def(&mut self) {
58 self.start_node(SyntaxKind::Def);
59
60 let span = self.current_span();
61 let name = token_text(self.source, &self.tokens[self.pos]);
62 self.bump();
63 self.validate_def_name(name, span);
64
65 self.peek();
66 let ate_equals = self.eat(SyntaxKind::Equals);
67 assert!(
68 ate_equals,
69 "parse_def: expected '=' but found {:?} (caller should verify Equals is present)",
70 self.current()
71 );
72
73 if EXPR_FIRST.contains(self.peek()) {
74 self.parse_expr();
75 } else {
76 self.error_msg(
77 DiagnosticKind::ExpectedExpression,
78 "after `=` in definition",
79 );
80 }
81
82 self.finish_node();
83 }
84
85 fn parse_expr_or_error(&mut self) -> bool {
88 let kind = self.peek();
89 if EXPR_FIRST.contains(kind) {
90 self.parse_expr();
91 true
92 } else if kind == SyntaxKind::At {
93 self.error_and_bump(DiagnosticKind::CaptureWithoutTarget);
94 false
95 } else if kind == SyntaxKind::Predicate {
96 self.error_and_bump(DiagnosticKind::UnsupportedPredicate);
97 false
98 } else {
99 self.error_and_bump_msg(
100 DiagnosticKind::UnexpectedToken,
101 "try `(node)`, `[a b]`, `{a b}`, `\"literal\"`, or `_`",
102 );
103 false
104 }
105 }
106
107 fn parse_expr(&mut self) {
109 self.parse_expr_inner(true)
110 }
111
112 fn parse_expr_no_suffix(&mut self) {
115 self.parse_expr_inner(false)
116 }
117
118 fn parse_expr_inner(&mut self, with_suffix: bool) {
119 if !self.enter_recursion() {
120 self.start_node(SyntaxKind::Error);
121 while !self.should_stop() {
122 self.bump();
123 }
124 self.finish_node();
125 return;
126 }
127
128 let checkpoint = self.checkpoint();
129
130 match self.peek() {
131 SyntaxKind::ParenOpen => self.parse_tree(),
132 SyntaxKind::BracketOpen => self.parse_alt(),
133 SyntaxKind::BraceOpen => self.parse_seq(),
134 SyntaxKind::Underscore => self.parse_wildcard(),
135 SyntaxKind::SingleQuote | SyntaxKind::DoubleQuote => self.parse_str(),
136 SyntaxKind::Dot => self.parse_anchor(),
137 SyntaxKind::Negation => self.parse_negated_field(),
138 SyntaxKind::Id => self.parse_tree_or_field(),
139 SyntaxKind::KwError | SyntaxKind::KwMissing => {
140 self.error_and_bump(DiagnosticKind::ErrorMissingOutsideParens);
141 }
142 _ => {
143 self.error_and_bump_msg(DiagnosticKind::UnexpectedToken, "not a valid expression");
144 }
145 }
146
147 if with_suffix {
148 self.try_parse_quantifier(checkpoint);
149 self.try_parse_capture(checkpoint);
150 }
151
152 self.exit_recursion();
153 }
154
155 fn parse_tree(&mut self) {
158 let checkpoint = self.checkpoint();
159 self.push_delimiter(SyntaxKind::ParenOpen);
160 self.bump(); let mut is_ref = false;
163 let mut ref_name: Option<String> = None;
164
165 match self.peek() {
166 SyntaxKind::ParenClose => {
167 self.start_node_at(checkpoint, SyntaxKind::Tree);
168 self.error(DiagnosticKind::EmptyTree);
169 self.pop_delimiter();
170 self.bump(); self.finish_node();
172 return;
173 }
174 SyntaxKind::Underscore => {
175 self.start_node_at(checkpoint, SyntaxKind::Tree);
176 self.bump();
177 }
178 SyntaxKind::Id => {
179 let name = token_text(self.source, &self.tokens[self.pos]).to_string();
180 let is_pascal_case = name.chars().next().is_some_and(|c| c.is_ascii_uppercase());
181 self.bump();
182
183 if is_pascal_case {
184 is_ref = true;
185 ref_name = Some(name);
186 } else {
187 self.start_node_at(checkpoint, SyntaxKind::Tree);
188 }
189
190 if self.peek() == SyntaxKind::Slash {
191 if is_ref {
192 self.start_node_at(checkpoint, SyntaxKind::Tree);
193 self.error(DiagnosticKind::InvalidSupertypeSyntax);
194 is_ref = false;
195 }
196 self.bump();
197 match self.peek() {
198 SyntaxKind::Id => {
199 self.bump();
200 }
201 SyntaxKind::SingleQuote | SyntaxKind::DoubleQuote => {
202 self.bump_string_tokens();
203 }
204 _ => {
205 self.error_msg(
206 DiagnosticKind::ExpectedSubtype,
207 "e.g., `expression/binary_expression`",
208 );
209 }
210 }
211 }
212 }
213 SyntaxKind::KwError => {
214 self.start_node_at(checkpoint, SyntaxKind::Tree);
215 self.bump();
216 if self.peek() != SyntaxKind::ParenClose {
217 self.error(DiagnosticKind::ErrorTakesNoArguments);
218 self.parse_children(SyntaxKind::ParenClose, TREE_RECOVERY);
219 }
220 self.pop_delimiter();
221 self.expect(SyntaxKind::ParenClose, "closing ')' for (ERROR)");
222 self.finish_node();
223 return;
224 }
225 SyntaxKind::KwMissing => {
226 self.start_node_at(checkpoint, SyntaxKind::Tree);
227 self.bump();
228 match self.peek() {
229 SyntaxKind::Id => {
230 self.bump();
231 }
232 SyntaxKind::SingleQuote | SyntaxKind::DoubleQuote => {
233 self.bump_string_tokens();
234 }
235 SyntaxKind::ParenClose => {}
236 _ => {
237 self.parse_children(SyntaxKind::ParenClose, TREE_RECOVERY);
238 }
239 }
240 self.pop_delimiter();
241 self.expect(SyntaxKind::ParenClose, "closing ')' for (MISSING)");
242 self.finish_node();
243 return;
244 }
245 _ => {
246 self.start_node_at(checkpoint, SyntaxKind::Tree);
247 }
248 }
249
250 let has_children = self.peek() != SyntaxKind::ParenClose;
251
252 if is_ref && has_children {
253 self.start_node_at(checkpoint, SyntaxKind::Tree);
254 let children_start = self.current_span().start();
255 self.parse_children(SyntaxKind::ParenClose, TREE_RECOVERY);
256 let children_end = self.last_non_trivia_end().unwrap_or(children_start);
257 let children_span = TextRange::new(children_start, children_end);
258
259 if let Some(name) = &ref_name {
260 self.diagnostics
261 .report(DiagnosticKind::RefCannotHaveChildren, children_span)
262 .message(name)
263 .emit();
264 }
265 } else if is_ref {
266 self.start_node_at(checkpoint, SyntaxKind::Ref);
267 } else {
268 self.parse_children(SyntaxKind::ParenClose, TREE_RECOVERY);
269 }
270
271 self.pop_delimiter();
272 self.expect(
273 SyntaxKind::ParenClose,
274 if is_ref && !has_children {
275 "closing ')' for reference"
276 } else {
277 "closing ')' for tree"
278 },
279 );
280 self.finish_node();
281 }
282
283 fn parse_children(&mut self, until: SyntaxKind, recovery: TokenSet) {
285 loop {
286 if self.eof() {
287 let (construct, delim, kind) = match until {
288 SyntaxKind::ParenClose => ("tree", "`)`", DiagnosticKind::UnclosedTree),
289 SyntaxKind::BraceClose => ("sequence", "`}`", DiagnosticKind::UnclosedSequence),
290 _ => panic!(
291 "parse_children: unexpected delimiter {:?} (only ParenClose/BraceClose supported)",
292 until
293 ),
294 };
295 let msg = format!("expected {delim}");
296 let open = self.delimiter_stack.last().unwrap_or_else(|| {
297 panic!(
298 "parse_children: unclosed {construct} at EOF but delimiter_stack is empty \
299 (caller must push delimiter before calling)"
300 )
301 });
302 self.error_unclosed_delimiter(
303 kind,
304 msg,
305 format!("{construct} started here"),
306 open.span,
307 );
308 break;
309 }
310 if self.has_fatal_error() {
311 break;
312 }
313 let kind = self.peek();
314 if kind == until {
315 break;
316 }
317 if SEPARATORS.contains(kind) {
318 self.error_skip_separator();
319 continue;
320 }
321 if EXPR_FIRST.contains(kind) {
322 self.parse_expr();
323 continue;
324 }
325 if kind == SyntaxKind::Predicate {
326 self.error_and_bump(DiagnosticKind::UnsupportedPredicate);
327 continue;
328 }
329 if recovery.contains(kind) {
330 break;
331 }
332 self.error_and_bump_msg(
333 DiagnosticKind::UnexpectedToken,
334 "not valid inside a node — try `(child)` or close with `)`",
335 );
336 }
337 }
338
339 fn parse_alt(&mut self) {
341 self.start_node(SyntaxKind::Alt);
342 self.push_delimiter(SyntaxKind::BracketOpen);
343 self.expect(SyntaxKind::BracketOpen, "opening '[' for alternation");
344
345 self.parse_alt_children();
346
347 self.pop_delimiter();
348 self.expect(SyntaxKind::BracketClose, "closing ']' for alternation");
349 self.finish_node();
350 }
351
352 fn parse_alt_children(&mut self) {
354 loop {
355 if self.eof() {
356 let msg = "expected `]`";
357 let open = self.delimiter_stack.last().unwrap_or_else(|| {
358 panic!(
359 "parse_alt_children: unclosed alternation at EOF but delimiter_stack is empty \
360 (caller must push delimiter before calling)"
361 )
362 });
363 self.error_unclosed_delimiter(
364 DiagnosticKind::UnclosedAlternation,
365 msg,
366 "alternation started here",
367 open.span,
368 );
369 break;
370 }
371 if self.has_fatal_error() {
372 break;
373 }
374 let kind = self.peek();
375 if kind == SyntaxKind::BracketClose {
376 break;
377 }
378 if SEPARATORS.contains(kind) {
379 self.error_skip_separator();
380 continue;
381 }
382
383 if kind == SyntaxKind::Id && self.peek_nth(1) == SyntaxKind::Colon {
385 let text = token_text(self.source, &self.tokens[self.pos]);
386 let first_char = text.chars().next().unwrap_or('a');
387 if first_char.is_ascii_uppercase() {
388 self.parse_branch();
389 } else {
390 self.parse_branch_lowercase_label();
391 }
392 continue;
393 }
394 if EXPR_FIRST.contains(kind) {
395 self.start_node(SyntaxKind::Branch);
396 self.parse_expr();
397 self.finish_node();
398 continue;
399 }
400 if ALT_RECOVERY.contains(kind) {
401 break;
402 }
403 self.error_and_bump_msg(
404 DiagnosticKind::UnexpectedToken,
405 "not valid inside alternation — try `(node)` or close with `]`",
406 );
407 }
408 }
409
410 fn parse_branch(&mut self) {
412 self.start_node(SyntaxKind::Branch);
413
414 let span = self.current_span();
415 let text = token_text(self.source, &self.tokens[self.pos]);
416 self.bump();
417 self.validate_branch_label(text, span);
418
419 self.peek();
420 self.expect(SyntaxKind::Colon, "':' after branch label");
421
422 if EXPR_FIRST.contains(self.peek()) {
423 self.parse_expr();
424 } else {
425 self.error_msg(DiagnosticKind::ExpectedExpression, "after `Label:`");
426 }
427
428 self.finish_node();
429 }
430
431 fn parse_branch_lowercase_label(&mut self) {
433 self.start_node(SyntaxKind::Branch);
434
435 let span = self.current_span();
436 let label_text = token_text(self.source, &self.tokens[self.pos]);
437 let capitalized = capitalize_first(label_text);
438
439 self.error_with_fix(
440 DiagnosticKind::LowercaseBranchLabel,
441 span,
442 "branch labels map to enum variants",
443 format!("use `{}`", capitalized),
444 capitalized,
445 );
446
447 self.bump();
448 self.peek();
449 self.expect(SyntaxKind::Colon, "':' after branch label");
450
451 if EXPR_FIRST.contains(self.peek()) {
452 self.parse_expr();
453 } else {
454 self.error_msg(DiagnosticKind::ExpectedExpression, "after `label:`");
455 }
456
457 self.finish_node();
458 }
459
460 fn parse_seq(&mut self) {
462 self.start_node(SyntaxKind::Seq);
463 self.push_delimiter(SyntaxKind::BraceOpen);
464 self.expect(SyntaxKind::BraceOpen, "opening '{' for sequence");
465
466 self.parse_children(SyntaxKind::BraceClose, SEQ_RECOVERY);
467
468 self.pop_delimiter();
469 self.expect(SyntaxKind::BraceClose, "closing '}' for sequence");
470 self.finish_node();
471 }
472
473 fn parse_wildcard(&mut self) {
474 self.start_node(SyntaxKind::Wildcard);
475 self.expect(SyntaxKind::Underscore, "'_' wildcard");
476 self.finish_node();
477 }
478
479 fn parse_str(&mut self) {
481 self.start_node(SyntaxKind::Str);
482 self.bump_string_tokens();
483 self.finish_node();
484 }
485
486 fn bump_string_tokens(&mut self) {
489 let open_quote = self.peek();
490 self.bump(); if self.peek() == SyntaxKind::StrVal {
493 self.bump(); }
495
496 let closing = self.peek();
497 assert_eq!(
498 closing, open_quote,
499 "bump_string_tokens: expected closing {:?} but found {:?} \
500 (lexer should only produce quote tokens from complete strings)",
501 open_quote, closing
502 );
503 self.bump();
504 }
505
506 fn parse_capture_suffix(&mut self) {
508 self.bump(); if self.peek() != SyntaxKind::Id {
511 self.error(DiagnosticKind::ExpectedCaptureName);
512 return;
513 }
514
515 let span = self.current_span();
516 let name = token_text(self.source, &self.tokens[self.pos]);
517 self.bump(); self.validate_capture_name(name, span);
520
521 if self.peek() == SyntaxKind::Colon {
522 self.parse_type_annotation_single_colon();
523 return;
524 }
525 if self.peek() == SyntaxKind::DoubleColon {
526 self.parse_type_annotation();
527 }
528 }
529
530 fn parse_type_annotation(&mut self) {
532 self.start_node(SyntaxKind::Type);
533 self.expect(SyntaxKind::DoubleColon, "'::' for type annotation");
534
535 if self.peek() == SyntaxKind::Id {
536 let span = self.current_span();
537 let text = token_text(self.source, &self.tokens[self.pos]);
538 self.bump();
539 self.validate_type_name(text, span);
540 } else {
541 self.error_msg(
542 DiagnosticKind::ExpectedTypeName,
543 "e.g., `::MyType` or `::string`",
544 );
545 }
546
547 self.finish_node();
548 }
549
550 fn parse_type_annotation_single_colon(&mut self) {
552 if self.peek_nth(1) != SyntaxKind::Id {
553 return;
554 }
555
556 self.start_node(SyntaxKind::Type);
557
558 let span = self.current_span();
559 self.error_with_fix(
560 DiagnosticKind::InvalidTypeAnnotationSyntax,
561 span,
562 "single `:` looks like a field",
563 "use `::`",
564 "::",
565 );
566
567 self.bump(); if self.peek() == SyntaxKind::Id {
571 self.bump();
572 }
573
574 self.finish_node();
575 }
576
577 fn parse_anchor(&mut self) {
579 self.start_node(SyntaxKind::Anchor);
580 self.expect(SyntaxKind::Dot, "'.' anchor");
581 self.finish_node();
582 }
583
584 fn parse_negated_field(&mut self) {
586 self.start_node(SyntaxKind::NegatedField);
587 self.expect(SyntaxKind::Negation, "'!' for negated field");
588
589 if self.peek() != SyntaxKind::Id {
590 self.error_msg(DiagnosticKind::ExpectedFieldName, "e.g., `!value`");
591 self.finish_node();
592 return;
593 }
594
595 let span = self.current_span();
596 let text = token_text(self.source, &self.tokens[self.pos]);
597 self.bump();
598 self.validate_field_name(text, span);
599 self.finish_node();
600 }
601
602 fn parse_tree_or_field(&mut self) {
605 if self.peek_nth(1) == SyntaxKind::Colon {
606 self.parse_field();
607 } else if self.peek_nth(1) == SyntaxKind::Equals {
608 self.parse_field_equals_typo();
609 } else {
610 self.error_and_bump_msg(
612 DiagnosticKind::BareIdentifier,
613 "wrap in parentheses: `(identifier)`",
614 );
615 }
616 }
617
618 fn parse_field(&mut self) {
620 self.start_node(SyntaxKind::Field);
621
622 let kind = self.peek();
623 assert_eq!(
624 kind,
625 SyntaxKind::Id,
626 "parse_field: expected Id but found {:?} (caller should verify Id is present)",
627 kind
628 );
629 let span = self.current_span();
630 let text = token_text(self.source, &self.tokens[self.pos]);
631 self.bump();
632 self.validate_field_name(text, span);
633
634 self.expect(
635 SyntaxKind::Colon,
636 "':' to separate field name from its value",
637 );
638
639 if EXPR_FIRST.contains(self.peek()) {
640 self.parse_expr_no_suffix();
641 } else {
642 self.error_msg(DiagnosticKind::ExpectedExpression, "after `field:`");
643 }
644
645 self.finish_node();
646 }
647
648 fn parse_field_equals_typo(&mut self) {
650 self.start_node(SyntaxKind::Field);
651
652 self.bump();
653 self.peek();
654 let span = self.current_span();
655 self.error_with_fix(
656 DiagnosticKind::InvalidFieldEquals,
657 span,
658 "this isn't a definition",
659 "use `:`",
660 ":",
661 );
662 self.bump();
663
664 if EXPR_FIRST.contains(self.peek()) {
665 self.parse_expr();
666 } else {
667 self.error_msg(DiagnosticKind::ExpectedExpression, "after `field =`");
668 }
669
670 self.finish_node();
671 }
672
673 fn error_skip_separator(&mut self) {
675 let kind = self.current();
676 let span = self.current_span();
677 let char_name = match kind {
679 SyntaxKind::Comma => ",",
680 SyntaxKind::Pipe => "|",
681 _ => panic!(
682 "error_skip_separator: unexpected token {:?} (only Comma/Pipe expected)",
683 kind
684 ),
685 };
686 self.error_with_fix(
687 DiagnosticKind::InvalidSeparator,
688 span,
689 format!("plotnik uses whitespace, not `{}`", char_name),
690 "remove",
691 "",
692 );
693 self.skip_token();
694 }
695
696 fn try_parse_quantifier(&mut self, checkpoint: Checkpoint) {
698 if self.at_set(QUANTIFIERS) {
699 self.start_node_at(checkpoint, SyntaxKind::Quantifier);
700 self.bump();
701 self.finish_node();
702 }
703 }
704
705 fn try_parse_capture(&mut self, checkpoint: Checkpoint) {
707 if self.peek() == SyntaxKind::At {
708 self.start_node_at(checkpoint, SyntaxKind::Capture);
709 self.drain_trivia();
710 self.parse_capture_suffix();
711 self.finish_node();
712 }
713 }
714
715 fn validate_capture_name(&mut self, name: &str, span: TextRange) {
717 if name.contains('.') {
718 let suggested = name.replace(['.', '-'], "_");
719 let suggested = to_snake_case(&suggested);
720 self.error_with_fix(
721 DiagnosticKind::CaptureNameHasDots,
722 span,
723 "captures become struct fields",
724 format!("use `@{}`", suggested),
725 suggested,
726 );
727 return;
728 }
729
730 if name.contains('-') {
731 let suggested = name.replace('-', "_");
732 let suggested = to_snake_case(&suggested);
733 self.error_with_fix(
734 DiagnosticKind::CaptureNameHasHyphens,
735 span,
736 "captures become struct fields",
737 format!("use `@{}`", suggested),
738 suggested,
739 );
740 return;
741 }
742
743 if name.chars().next().is_some_and(|c| c.is_ascii_uppercase()) {
744 let suggested = to_snake_case(name);
745 self.error_with_fix(
746 DiagnosticKind::CaptureNameUppercase,
747 span,
748 "captures become struct fields",
749 format!("use `@{}`", suggested),
750 suggested,
751 );
752 }
753 }
754
755 fn validate_def_name(&mut self, name: &str, span: TextRange) {
757 if !name.chars().next().is_some_and(|c| c.is_ascii_uppercase()) {
758 let suggested = to_pascal_case(name);
759 self.error_with_fix(
760 DiagnosticKind::DefNameLowercase,
761 span,
762 "definitions map to types",
763 format!("use `{}`", suggested),
764 suggested,
765 );
766 return;
767 }
768
769 if name.contains('_') || name.contains('-') || name.contains('.') {
770 let suggested = to_pascal_case(name);
771 self.error_with_fix(
772 DiagnosticKind::DefNameHasSeparators,
773 span,
774 "definitions map to types",
775 format!("use `{}`", suggested),
776 suggested,
777 );
778 }
779 }
780
781 fn validate_branch_label(&mut self, name: &str, span: TextRange) {
783 if name.contains('_') || name.contains('-') || name.contains('.') {
784 let suggested = to_pascal_case(name);
785 self.error_with_fix(
786 DiagnosticKind::BranchLabelHasSeparators,
787 span,
788 "branch labels map to enum variants",
789 format!("use `{}:`", suggested),
790 format!("{}:", suggested),
791 );
792 }
793 }
794
795 fn validate_field_name(&mut self, name: &str, span: TextRange) {
797 if name.contains('.') {
798 let suggested = name.replace(['.', '-'], "_");
799 let suggested = to_snake_case(&suggested);
800 self.error_with_fix(
801 DiagnosticKind::FieldNameHasDots,
802 span,
803 "field names become struct fields",
804 format!("use `{}:`", suggested),
805 format!("{}:", suggested),
806 );
807 return;
808 }
809
810 if name.contains('-') {
811 let suggested = name.replace('-', "_");
812 let suggested = to_snake_case(&suggested);
813 self.error_with_fix(
814 DiagnosticKind::FieldNameHasHyphens,
815 span,
816 "field names become struct fields",
817 format!("use `{}:`", suggested),
818 format!("{}:", suggested),
819 );
820 return;
821 }
822
823 if name.chars().next().is_some_and(|c| c.is_ascii_uppercase()) {
824 let suggested = to_snake_case(name);
825 self.error_with_fix(
826 DiagnosticKind::FieldNameUppercase,
827 span,
828 "field names become struct fields",
829 format!("use `{}:`", suggested),
830 format!("{}:", suggested),
831 );
832 }
833 }
834
835 fn validate_type_name(&mut self, name: &str, span: TextRange) {
837 if name.contains('.') || name.contains('-') {
838 let suggested = to_pascal_case(name);
839 self.error_with_fix(
840 DiagnosticKind::TypeNameInvalidChars,
841 span,
842 "type annotations map to types",
843 format!("use `::{}`", suggested),
844 format!("::{}", suggested),
845 );
846 }
847 }
848}
849
850fn to_snake_case(s: &str) -> String {
851 let mut result = String::new();
852 for (i, c) in s.chars().enumerate() {
853 if c.is_ascii_uppercase() {
854 if i > 0 && !result.ends_with('_') {
855 result.push('_');
856 }
857 result.push(c.to_ascii_lowercase());
858 } else {
859 result.push(c);
860 }
861 }
862 result
863}
864
865fn to_pascal_case(s: &str) -> String {
866 let mut result = String::new();
867 let mut capitalize_next = true;
868 for c in s.chars() {
869 if c == '_' || c == '-' || c == '.' {
870 capitalize_next = true;
871 } else if capitalize_next {
872 result.push(c.to_ascii_uppercase());
873 capitalize_next = false;
874 } else {
875 result.push(c.to_ascii_lowercase());
876 }
877 }
878 result
879}
880
881fn capitalize_first(s: &str) -> String {
882 assert!(!s.is_empty(), "capitalize_first: called with empty string");
883 let mut chars = s.chars();
884 let c = chars.next().unwrap();
885 c.to_uppercase().chain(chars).collect()
886}