1use crate::code_node::{CodeNode, parts_args_to_nodes};
2use crate::import::ImportRef;
3use crate::lang::CodeLang;
4use crate::type_name::TypeName;
5
6#[derive(Debug, Clone, PartialEq, serde::Serialize, serde::Deserialize)]
8pub(crate) enum FormatPart {
9 Literal(String),
11 Type,
13 Name,
15 StringLit,
17 Literal_,
19 Wrap,
21 Indent,
23 Dedent,
25 StatementBegin,
27 StatementEnd,
29 Newline,
31 BlockOpen,
34 BlockOpenOverride(String),
38 BlockClose,
42 BlockCloseTransition,
46}
47
48#[derive(Debug, Clone, serde::Serialize, serde::Deserialize)]
50pub enum Arg {
51 TypeName(TypeName),
53 Name(String),
55 StringLit(String),
57 Literal(String),
59 Code(CodeBlock),
61}
62
63#[derive(Debug, Clone, serde::Serialize, serde::Deserialize)]
92pub struct CodeBlock {
93 pub(crate) nodes: Vec<CodeNode>,
94}
95
96impl CodeBlock {
97 pub fn builder() -> CodeBlockBuilder {
99 CodeBlockBuilder::new()
100 }
101
102 pub fn of(format: &str, args: impl IntoArgs) -> Result<Self, crate::error::SigilStitchError> {
104 let mut builder = CodeBlockBuilder::new();
105 builder.add(format, args);
106 builder.build()
107 }
108
109 pub fn is_empty(&self) -> bool {
111 self.nodes.is_empty()
112 }
113
114 pub(crate) fn ends_with_newline_or_block_close(&self) -> bool {
116 fn check_last(nodes: &[CodeNode]) -> bool {
117 match nodes.last() {
118 Some(CodeNode::Newline | CodeNode::BlockClose) => true,
119 Some(CodeNode::Sequence(children)) => check_last(children),
120 _ => false,
121 }
122 }
123 check_last(&self.nodes)
124 }
125
126 pub fn collect_imports(&self, out: &mut Vec<ImportRef>) {
128 collect_imports_from_nodes(&self.nodes, out);
129 }
130
131 pub fn render_standalone(
137 &self,
138 lang: &dyn CodeLang,
139 width: usize,
140 ) -> Result<String, crate::error::SigilStitchError> {
141 let imports = crate::import::ImportGroup::new();
142 let mut renderer = crate::code_renderer::CodeRenderer::new(lang, &imports, width);
143 renderer.render(self)
144 }
145}
146
147fn collect_imports_from_nodes(nodes: &[CodeNode], out: &mut Vec<ImportRef>) {
148 for node in nodes {
149 match node {
150 CodeNode::TypeRef(tn) => tn.collect_imports(out),
151 CodeNode::Nested(block) => block.collect_imports(out),
152 CodeNode::Sequence(children) => collect_imports_from_nodes(children, out),
153 _ => {}
154 }
155 }
156}
157
158#[derive(Debug)]
180pub struct CodeBlockBuilder {
181 nodes: Vec<CodeNode>,
182 indent_depth: i32,
183 errors: Vec<crate::error::SigilStitchError>,
184}
185
186impl CodeBlockBuilder {
187 pub fn new() -> Self {
189 Self {
190 nodes: Vec::new(),
191 indent_depth: 0,
192 errors: Vec::new(),
193 }
194 }
195
196 pub fn add(&mut self, format: &str, args: impl IntoArgs) -> &mut Self {
198 let arg_vec = args.into_args();
199 let parsed = match parse_format(format) {
200 Ok(parts) => parts,
201 Err(err) => {
202 self.errors.push(err);
203 return self;
204 }
205 };
206
207 let consuming_specifiers: Vec<String> = parsed
208 .iter()
209 .filter_map(|p| match p {
210 FormatPart::Type => Some("%T".to_string()),
211 FormatPart::Name => Some("%N".to_string()),
212 FormatPart::StringLit => Some("%S".to_string()),
213 FormatPart::Literal_ => Some("%L".to_string()),
214 _ => None,
215 })
216 .collect();
217
218 let expected_args = consuming_specifiers.len();
219
220 if expected_args != arg_vec.len() {
221 let actual_arg_kinds: Vec<String> = arg_vec
222 .iter()
223 .map(|a| match a {
224 Arg::TypeName(_) => "TypeName".to_string(),
225 Arg::Name(_) => "Name".to_string(),
226 Arg::StringLit(_) => "StringLit".to_string(),
227 Arg::Literal(_) => "Literal".to_string(),
228 Arg::Code(_) => "Code".to_string(),
229 })
230 .collect();
231 self.errors
232 .push(crate::error::SigilStitchError::FormatArgCount {
233 format: format.to_string(),
234 expected: expected_args,
235 actual: arg_vec.len(),
236 expected_specifiers: consuming_specifiers,
237 actual_arg_kinds,
238 });
239 return self;
240 }
241
242 let new_nodes = parts_args_to_nodes(&parsed, &arg_vec);
243 self.nodes.extend(new_nodes);
244 self
245 }
246
247 pub fn add_statement(&mut self, format: &str, args: impl IntoArgs) -> &mut Self {
249 self.nodes.push(CodeNode::StatementBegin);
250 self.add(format, args);
251 self.nodes.push(CodeNode::StatementEnd);
252 self.nodes.push(CodeNode::Newline);
253 self
254 }
255
256 pub fn begin_control_flow(&mut self, format: &str, args: impl IntoArgs) -> &mut Self {
258 self.add(format, args);
259 self.nodes.push(CodeNode::BlockOpen);
260 self.nodes.push(CodeNode::Newline);
261 self.nodes.push(CodeNode::Indent);
262 self.indent_depth += 1;
263 self
264 }
265
266 pub fn begin_control_flow_with_open(
272 &mut self,
273 format: &str,
274 args: impl IntoArgs,
275 custom_open: &str,
276 ) -> &mut Self {
277 self.add(format, args);
278 if !custom_open.is_empty() {
279 self.nodes
280 .push(CodeNode::BlockOpenOverride(custom_open.to_string()));
281 }
282 self.nodes.push(CodeNode::Newline);
283 self.nodes.push(CodeNode::Indent);
284 self.indent_depth += 1;
285 self
286 }
287
288 pub fn next_control_flow(&mut self, format: &str, args: impl IntoArgs) -> &mut Self {
290 self.nodes.push(CodeNode::Dedent);
291 self.indent_depth -= 1;
292 self.nodes.push(CodeNode::BlockCloseTransition);
293 self.add(format, args);
294 self.nodes.push(CodeNode::BlockOpen);
295 self.nodes.push(CodeNode::Newline);
296 self.nodes.push(CodeNode::Indent);
297 self.indent_depth += 1;
298 self
299 }
300
301 pub fn end_control_flow(&mut self) -> &mut Self {
303 self.nodes.push(CodeNode::Dedent);
304 self.indent_depth -= 1;
305 self.nodes.push(CodeNode::BlockClose);
306 self
307 }
308
309 pub fn add_line(&mut self) -> &mut Self {
311 self.nodes.push(CodeNode::Newline);
312 self
313 }
314
315 pub fn add_comment(&mut self, text: &str) -> &mut Self {
317 self.nodes.push(CodeNode::Comment(text.to_string()));
318 self.nodes.push(CodeNode::Newline);
319 self
320 }
321
322 pub fn add_code(&mut self, block: CodeBlock) -> &mut Self {
324 self.nodes.push(CodeNode::Nested(block));
325 self
326 }
327
328 pub fn build(self) -> Result<CodeBlock, crate::error::SigilStitchError> {
334 if let Some(err) = self.errors.into_iter().next() {
335 return Err(err);
336 }
337 if self.indent_depth != 0 {
338 return Err(crate::error::SigilStitchError::UnbalancedIndent {
339 depth: self.indent_depth,
340 });
341 }
342 Ok(CodeBlock { nodes: self.nodes })
343 }
344
345 pub fn build_unwrap(self) -> CodeBlock {
347 self.build().unwrap()
348 }
349}
350
351impl Default for CodeBlockBuilder {
352 fn default() -> Self {
353 Self::new()
354 }
355}
356
357fn parse_format(format: &str) -> Result<Vec<FormatPart>, crate::error::SigilStitchError> {
359 let mut parts = Vec::new();
360 let mut current_literal = String::new();
361 let mut chars = format.char_indices().peekable();
362
363 while let Some(&(_, ch)) = chars.peek() {
364 if ch == '%' {
365 chars.next();
366 if let Some(&(_, spec)) = chars.peek() {
367 chars.next();
368 let part = match spec {
369 'T' => Some(FormatPart::Type),
370 'N' => Some(FormatPart::Name),
371 'S' => Some(FormatPart::StringLit),
372 'L' => Some(FormatPart::Literal_),
373 'W' => Some(FormatPart::Wrap),
374 '>' => Some(FormatPart::Indent),
375 '<' => Some(FormatPart::Dedent),
376 '[' => Some(FormatPart::StatementBegin),
377 ']' => Some(FormatPart::StatementEnd),
378 '%' => {
379 current_literal.push('%');
380 continue;
381 }
382 _ => {
383 return Err(crate::error::SigilStitchError::InvalidFormatSpecifier {
384 format: format.to_string(),
385 specifier: spec,
386 });
387 }
388 };
389 if let Some(part) = part {
390 if !current_literal.is_empty() {
391 parts.push(FormatPart::Literal(std::mem::take(&mut current_literal)));
392 }
393 parts.push(part);
394 }
395 }
396 } else if ch == '\n' {
397 chars.next();
398 if !current_literal.is_empty() {
399 parts.push(FormatPart::Literal(std::mem::take(&mut current_literal)));
400 }
401 parts.push(FormatPart::Newline);
402 } else {
403 chars.next();
404 current_literal.push(ch);
405 }
406 }
407
408 if !current_literal.is_empty() {
409 parts.push(FormatPart::Literal(current_literal));
410 }
411
412 Ok(parts)
413}
414
415pub trait IntoArgs {
424 fn into_args(self) -> Vec<Arg>;
426}
427
428impl IntoArgs for () {
430 fn into_args(self) -> Vec<Arg> {
431 Vec::new()
432 }
433}
434
435impl IntoArgs for TypeName {
437 fn into_args(self) -> Vec<Arg> {
438 vec![Arg::TypeName(self)]
439 }
440}
441
442impl IntoArgs for &str {
444 fn into_args(self) -> Vec<Arg> {
445 vec![Arg::Literal(self.to_string())]
446 }
447}
448
449impl IntoArgs for String {
450 fn into_args(self) -> Vec<Arg> {
451 vec![Arg::Literal(self)]
452 }
453}
454
455impl IntoArgs for CodeBlock {
457 fn into_args(self) -> Vec<Arg> {
458 vec![Arg::Code(self)]
459 }
460}
461
462impl IntoArgs for Vec<Arg> {
464 fn into_args(self) -> Vec<Arg> {
465 self
466 }
467}
468
469pub struct NameArg(pub String);
485
486impl IntoArgs for NameArg {
487 fn into_args(self) -> Vec<Arg> {
488 vec![Arg::Name(self.0)]
489 }
490}
491
492pub struct StringLitArg(pub String);
508
509impl IntoArgs for StringLitArg {
510 fn into_args(self) -> Vec<Arg> {
511 vec![Arg::StringLit(self.0)]
512 }
513}
514
515impl From<TypeName> for Arg {
517 fn from(tn: TypeName) -> Self {
518 Arg::TypeName(tn)
519 }
520}
521
522impl From<&str> for Arg {
523 fn from(s: &str) -> Self {
524 Arg::Literal(s.to_string())
525 }
526}
527
528impl From<String> for Arg {
529 fn from(s: String) -> Self {
530 Arg::Literal(s)
531 }
532}
533
534impl From<CodeBlock> for Arg {
535 fn from(cb: CodeBlock) -> Self {
536 Arg::Code(cb)
537 }
538}
539
540impl From<NameArg> for Arg {
541 fn from(n: NameArg) -> Self {
542 Arg::Name(n.0)
543 }
544}
545
546impl From<StringLitArg> for Arg {
547 fn from(s: StringLitArg) -> Self {
548 Arg::StringLit(s.0)
549 }
550}
551
552macro_rules! impl_into_args_tuple {
556 ($($idx:tt $T:ident),+) => {
557 impl<$($T: Into<Arg>),+> IntoArgs for ($($T,)+) {
558 fn into_args(self) -> Vec<Arg> {
559 vec![$(self.$idx.into()),+]
560 }
561 }
562 };
563}
564
565impl_into_args_tuple!(0 A);
566impl_into_args_tuple!(0 A, 1 B);
567impl_into_args_tuple!(0 A, 1 B, 2 C);
568impl_into_args_tuple!(0 A, 1 B, 2 C, 3 D);
569impl_into_args_tuple!(0 A, 1 B, 2 C, 3 D, 4 E);
570impl_into_args_tuple!(0 A, 1 B, 2 C, 3 D, 4 E, 5 F);
571impl_into_args_tuple!(0 A, 1 B, 2 C, 3 D, 4 E, 5 F, 6 G);
572impl_into_args_tuple!(0 A, 1 B, 2 C, 3 D, 4 E, 5 F, 6 G, 7 H);
573
574#[cfg(test)]
575mod tests {
576 use super::*;
577 use crate::code_node::CodeNode;
578
579 #[test]
580 fn test_parse_all_specifiers() {
581 let parts = parse_format("hello %T world %N %S %L %W %> %< %[ %]").unwrap();
582 assert!(parts.contains(&FormatPart::Type));
583 assert!(parts.contains(&FormatPart::Name));
584 assert!(parts.contains(&FormatPart::StringLit));
585 assert!(parts.contains(&FormatPart::Literal_));
586 assert!(parts.contains(&FormatPart::Wrap));
587 assert!(parts.contains(&FormatPart::Indent));
588 assert!(parts.contains(&FormatPart::Dedent));
589 assert!(parts.contains(&FormatPart::StatementBegin));
590 assert!(parts.contains(&FormatPart::StatementEnd));
591 }
592
593 #[test]
594 fn test_parse_literal_percent() {
595 let parts = parse_format("100%%").unwrap();
596 assert_eq!(parts, vec![FormatPart::Literal("100%".to_string())]);
597 }
598
599 #[test]
600 fn test_parse_empty() {
601 let parts = parse_format("").unwrap();
602 assert!(parts.is_empty());
603 }
604
605 #[test]
606 fn test_parse_newlines() {
607 let parts = parse_format("line1\nline2").unwrap();
608 assert_eq!(
609 parts,
610 vec![
611 FormatPart::Literal("line1".to_string()),
612 FormatPart::Newline,
613 FormatPart::Literal("line2".to_string()),
614 ]
615 );
616 }
617
618 #[test]
619 fn test_builder_add_statement() {
620 let mut b = CodeBlock::builder();
621 b.add_statement("const x = %L", "42");
622 let block = b.build().unwrap();
623
624 assert!(!block.is_empty());
625 let has_stmt_begin = block
626 .nodes
627 .iter()
628 .any(|n| matches!(n, CodeNode::StatementBegin));
629 let has_stmt_end = block
630 .nodes
631 .iter()
632 .any(|n| matches!(n, CodeNode::StatementEnd));
633 assert!(has_stmt_begin);
634 assert!(has_stmt_end);
635 }
636
637 #[test]
638 fn test_builder_control_flow() {
639 let mut b = CodeBlock::builder();
640 b.begin_control_flow("if (x > 0)", ());
641 b.add_statement("return x", ());
642 b.end_control_flow();
643 let block = b.build().unwrap();
644
645 assert!(!block.is_empty());
646 }
647
648 #[test]
649 fn test_builder_unbalanced_control_flow() {
650 let mut b = CodeBlock::builder();
651 b.begin_control_flow("if (x)", ());
652 b.add_statement("y()", ());
653 let result = b.build();
655 assert!(result.is_err());
656 assert!(result.unwrap_err().to_string().contains("unbalanced"));
657 }
658
659 #[test]
660 fn test_mismatched_arg_count() {
661 let mut b = CodeBlock::builder();
662 b.add("%T", ());
663 let result = b.build();
664 assert!(result.is_err());
665 assert!(
666 result
667 .unwrap_err()
668 .to_string()
669 .contains("expects 1 args but got 0")
670 );
671 }
672
673 #[test]
674 fn test_into_args_tuple() {
675 let user = TypeName::importable("./models", "User");
676 let args: Vec<Arg> = (user, "hello").into_args();
677 assert_eq!(args.len(), 2);
678 assert!(matches!(&args[0], Arg::TypeName(_)));
679 assert!(matches!(&args[1], Arg::Literal(s) if s == "hello"));
680 }
681
682 #[test]
683 fn test_into_args_single_typename() {
684 let user = TypeName::importable("./models", "User");
685 let args: Vec<Arg> = user.into_args();
686 assert_eq!(args.len(), 1);
687 }
688
689 #[test]
690 fn test_into_args_single_str() {
691 let args: Vec<Arg> = "hello".into_args();
692 assert_eq!(args.len(), 1);
693 assert!(matches!(&args[0], Arg::Literal(s) if s == "hello"));
694 }
695
696 #[test]
697 fn test_collect_imports_from_codeblock() {
698 let user = TypeName::importable("./models", "User");
699 let tag = TypeName::importable("./models", "Tag");
700 let mut b = CodeBlock::builder();
701 b.add_statement("const u: %T = getUser()", (user,));
702 b.add_statement("const t: %T = getTag()", (tag,));
703 let block = b.build().unwrap();
704
705 let mut imports = Vec::new();
706 block.collect_imports(&mut imports);
707 assert_eq!(imports.len(), 2);
708 assert_eq!(imports[0].name, "User");
709 assert_eq!(imports[1].name, "Tag");
710 }
711
712 #[test]
713 fn test_nested_codeblock_imports() {
714 let user = TypeName::importable("./models", "User");
715 let mut ib = CodeBlock::builder();
716 ib.add_statement("return new %T()", (user,));
717 let inner = ib.build().unwrap();
718
719 let mut ob = CodeBlock::builder();
720 ob.add_code(inner);
721 let outer = ob.build().unwrap();
722
723 let mut imports = Vec::new();
724 outer.collect_imports(&mut imports);
725 assert_eq!(imports.len(), 1);
726 assert_eq!(imports[0].name, "User");
727 }
728
729 #[test]
730 fn test_name_arg() {
731 let mut b = CodeBlock::builder();
732 b.add("this.%N()", (NameArg("getUser".to_string()),));
733 let block = b.build().unwrap();
734 let has_name = block
735 .nodes
736 .iter()
737 .any(|n| matches!(n, CodeNode::NameRef(s) if s == "getUser"));
738 assert!(has_name);
739 }
740
741 #[test]
742 fn test_string_lit_arg() {
743 let mut b = CodeBlock::builder();
744 b.add("const x = %S", (StringLitArg("hello".to_string()),));
745 let block = b.build().unwrap();
746 let has_str_lit = block
747 .nodes
748 .iter()
749 .any(|n| matches!(n, CodeNode::StringLit(s) if s == "hello"));
750 assert!(has_str_lit);
751 }
752
753 #[test]
754 fn test_invalid_format_specifier() {
755 let mut b = CodeBlock::builder();
756 b.add("hello %X world", ());
757 let result = b.build();
758 assert!(result.is_err());
759 let err_msg = result.unwrap_err().to_string();
760 assert!(err_msg.contains("invalid format specifier"));
761 assert!(err_msg.contains("%X"));
762 }
763
764 #[test]
765 fn test_parse_format_invalid_specifier_returns_error() {
766 let result = parse_format("foo %Z bar");
767 assert!(result.is_err());
768 let err_msg = result.unwrap_err().to_string();
769 assert!(err_msg.contains("invalid format specifier"));
770 assert!(err_msg.contains("%Z"));
771 }
772
773 #[test]
774 fn test_mismatched_arg_count_includes_specifiers_and_kinds() {
775 let user = TypeName::importable("./models", "User");
776 let mut b = CodeBlock::builder();
777 b.add("%T %S %L", (user,));
778 let result = b.build();
779 assert!(result.is_err());
780 let err_msg = result.unwrap_err().to_string();
781 assert!(err_msg.contains("expects 3 args but got 1"));
782 assert!(err_msg.contains("%T"));
783 assert!(err_msg.contains("%S"));
784 assert!(err_msg.contains("%L"));
785 assert!(err_msg.contains("TypeName"));
786 }
787
788 #[test]
789 fn test_begin_control_flow_with_open_non_empty() {
790 let mut b = CodeBlock::builder();
791 b.begin_control_flow_with_open("class Functor f", (), " where");
792 b.add_statement("fmap :: (a -> b) -> f a -> f b", ());
793 b.end_control_flow();
794 let block = b.build().unwrap();
795 let has_override = block
796 .nodes
797 .iter()
798 .any(|n| matches!(n, CodeNode::BlockOpenOverride(s) if s == " where"));
799 assert!(has_override, "should contain BlockOpenOverride(\" where\")");
800 let has_block_open = block.nodes.iter().any(|n| matches!(n, CodeNode::BlockOpen));
801 assert!(
802 !has_block_open,
803 "should NOT contain BlockOpen when override is used"
804 );
805 }
806
807 #[test]
808 fn test_begin_control_flow_with_open_empty() {
809 let mut b = CodeBlock::builder();
810 b.begin_control_flow_with_open("match x with", (), "");
811 b.add("| Red -> red", ());
812 b.add_line();
813 b.end_control_flow();
814 let block = b.build().unwrap();
815 let has_override = block
816 .nodes
817 .iter()
818 .any(|n| matches!(n, CodeNode::BlockOpenOverride(_)));
819 assert!(
820 !has_override,
821 "empty custom_open should skip BlockOpenOverride"
822 );
823 let has_block_open = block.nodes.iter().any(|n| matches!(n, CodeNode::BlockOpen));
824 assert!(!has_block_open, "should NOT contain BlockOpen either");
825 }
826}