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, Copy, PartialEq, Eq, serde::Serialize, serde::Deserialize)]
18pub enum Specifier {
19 Type,
21 Name,
23 StringLit,
25 Literal,
27}
28
29impl Specifier {
30 pub fn from_format_char(ch: char) -> Option<Self> {
35 match ch {
36 'T' => Some(Self::Type),
37 'N' => Some(Self::Name),
38 'S' => Some(Self::StringLit),
39 'L' => Some(Self::Literal),
40 _ => None,
41 }
42 }
43
44 pub fn format_char(self) -> char {
46 match self {
47 Self::Type => 'T',
48 Self::Name => 'N',
49 Self::StringLit => 'S',
50 Self::Literal => 'L',
51 }
52 }
53
54 pub fn all() -> &'static [Self] {
56 &[Self::Type, Self::Name, Self::StringLit, Self::Literal]
57 }
58}
59
60#[derive(Debug, Clone, PartialEq, serde::Serialize, serde::Deserialize)]
62pub(crate) enum FormatPart {
63 Literal(String),
65 Arg(Specifier),
67 Wrap,
69 Indent,
71 Dedent,
73 StatementBegin,
75 StatementEnd,
77 Newline,
79 BlockOpen,
82 BlockOpenOverride(String),
86 BlockClose,
90 BlockCloseTransition,
94}
95
96#[derive(Debug, Clone, serde::Serialize, serde::Deserialize)]
98pub enum Arg {
99 TypeName(TypeName),
101 Name(String),
103 StringLit(String),
105 Literal(String),
107 Code(CodeBlock),
109}
110
111#[derive(Debug, Clone, serde::Serialize, serde::Deserialize)]
140pub struct CodeBlock {
141 pub(crate) nodes: Vec<CodeNode>,
142}
143
144impl CodeBlock {
145 pub fn builder() -> CodeBlockBuilder {
147 CodeBlockBuilder::new()
148 }
149
150 pub fn of(format: &str, args: impl IntoArgs) -> Result<Self, crate::error::SigilStitchError> {
152 let mut builder = CodeBlockBuilder::new();
153 builder.add(format, args);
154 builder.build()
155 }
156
157 pub fn is_empty(&self) -> bool {
159 self.nodes.is_empty()
160 }
161
162 pub fn ends_with_newline_or_block_close(&self) -> bool {
164 fn check_last(nodes: &[CodeNode]) -> bool {
165 match nodes.last() {
166 Some(CodeNode::Newline | CodeNode::BlockClose) => true,
167 Some(CodeNode::Sequence(children)) => check_last(children),
168 Some(CodeNode::Nested(inner)) => check_last(&inner.nodes),
169 _ => false,
170 }
171 }
172 check_last(&self.nodes)
173 }
174
175 pub fn collect_imports(&self, out: &mut Vec<ImportRef>) {
177 crate::import_collector::walk_nodes(&self.nodes, out);
178 }
179
180 pub fn render_standalone(
186 &self,
187 lang: &dyn CodeLang,
188 width: usize,
189 ) -> Result<String, crate::error::SigilStitchError> {
190 let imports = crate::import::ImportGroup::new();
191 let mut renderer = crate::code_renderer::CodeRenderer::new(lang, &imports, width);
192 renderer.render(self)
193 }
194}
195
196#[derive(Debug)]
218pub struct CodeBlockBuilder {
219 nodes: Vec<CodeNode>,
220 indent_depth: i32,
221 errors: Vec<crate::error::SigilStitchError>,
222}
223
224impl CodeBlockBuilder {
225 pub fn new() -> Self {
227 Self {
228 nodes: Vec::new(),
229 indent_depth: 0,
230 errors: Vec::new(),
231 }
232 }
233
234 pub fn add(&mut self, format: &str, args: impl IntoArgs) -> &mut Self {
236 let arg_vec = args.into_args();
237 let parsed = match parse_format(format) {
238 Ok(parts) => parts,
239 Err(err) => {
240 self.errors.push(err);
241 return self;
242 }
243 };
244
245 let consuming_specifiers: Vec<String> = parsed
246 .iter()
247 .filter_map(|p| match p {
248 FormatPart::Arg(s) => Some(format!("%{}", s.format_char())),
249 _ => None,
250 })
251 .collect();
252
253 let expected_args = consuming_specifiers.len();
254
255 if expected_args != arg_vec.len() {
256 let actual_arg_kinds: Vec<String> = arg_vec
257 .iter()
258 .map(|a| match a {
259 Arg::TypeName(_) => "TypeName".to_string(),
260 Arg::Name(_) => "Name".to_string(),
261 Arg::StringLit(_) => "StringLit".to_string(),
262 Arg::Literal(_) => "Literal".to_string(),
263 Arg::Code(_) => "Code".to_string(),
264 })
265 .collect();
266 self.errors
267 .push(crate::error::SigilStitchError::FormatArgCount {
268 format: format.to_string(),
269 expected: expected_args,
270 actual: arg_vec.len(),
271 expected_specifiers: consuming_specifiers,
272 actual_arg_kinds,
273 });
274 return self;
275 }
276
277 let new_nodes = parts_args_to_nodes(&parsed, &arg_vec);
278 self.nodes.extend(new_nodes);
279 self
280 }
281
282 pub fn add_statement(&mut self, format: &str, args: impl IntoArgs) -> &mut Self {
284 self.nodes.push(CodeNode::StatementBegin);
285 self.add(format, args);
286 self.nodes.push(CodeNode::StatementEnd);
287 self.nodes.push(CodeNode::Newline);
288 self
289 }
290
291 pub fn begin_control_flow(&mut self, format: &str, args: impl IntoArgs) -> &mut Self {
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 begin_control_flow_with_open(
307 &mut self,
308 format: &str,
309 args: impl IntoArgs,
310 custom_open: &str,
311 ) -> &mut Self {
312 self.add(format, args);
313 if !custom_open.is_empty() {
314 self.nodes
315 .push(CodeNode::BlockOpenOverride(custom_open.to_string()));
316 }
317 self.nodes.push(CodeNode::Newline);
318 self.nodes.push(CodeNode::Indent);
319 self.indent_depth += 1;
320 self
321 }
322
323 pub fn next_control_flow(&mut self, format: &str, args: impl IntoArgs) -> &mut Self {
325 self.nodes.push(CodeNode::Dedent);
326 self.indent_depth -= 1;
327 self.nodes.push(CodeNode::BlockCloseTransition);
328 self.add(format, args);
329 self.nodes.push(CodeNode::BlockOpen);
330 self.nodes.push(CodeNode::Newline);
331 self.nodes.push(CodeNode::Indent);
332 self.indent_depth += 1;
333 self
334 }
335
336 pub fn end_control_flow(&mut self) -> &mut Self {
338 self.nodes.push(CodeNode::Dedent);
339 self.indent_depth -= 1;
340 self.nodes.push(CodeNode::BlockClose);
341 self
342 }
343
344 pub fn add_line(&mut self) -> &mut Self {
346 self.nodes.push(CodeNode::Newline);
347 self
348 }
349
350 pub fn add_comment(&mut self, text: &str) -> &mut Self {
352 self.nodes.push(CodeNode::Comment(text.to_string()));
353 self.nodes.push(CodeNode::Newline);
354 self
355 }
356
357 pub fn add_code(&mut self, block: CodeBlock) -> &mut Self {
359 self.nodes.push(CodeNode::Nested(block));
360 self
361 }
362
363 pub fn build(self) -> Result<CodeBlock, crate::error::SigilStitchError> {
369 if let Some(err) = self.errors.into_iter().next() {
370 return Err(err);
371 }
372 if self.indent_depth != 0 {
373 return Err(crate::error::SigilStitchError::UnbalancedIndent {
374 depth: self.indent_depth,
375 });
376 }
377 Ok(CodeBlock { nodes: self.nodes })
378 }
379
380 pub fn build_unwrap(self) -> CodeBlock {
382 self.build().unwrap()
383 }
384}
385
386impl Default for CodeBlockBuilder {
387 fn default() -> Self {
388 Self::new()
389 }
390}
391
392fn parse_format(format: &str) -> Result<Vec<FormatPart>, crate::error::SigilStitchError> {
394 let mut parts = Vec::new();
395 let mut current_literal = String::new();
396 let mut chars = format.char_indices().peekable();
397
398 while let Some(&(_, ch)) = chars.peek() {
399 if ch == '%' {
400 chars.next();
401 if let Some(&(_, spec)) = chars.peek() {
402 chars.next();
403 let part = match spec {
404 'W' => Some(FormatPart::Wrap),
405 '>' => Some(FormatPart::Indent),
406 '<' => Some(FormatPart::Dedent),
407 '[' => Some(FormatPart::StatementBegin),
408 ']' => Some(FormatPart::StatementEnd),
409 '%' => {
410 current_literal.push('%');
411 continue;
412 }
413 _ => match Specifier::from_format_char(spec) {
414 Some(s) => Some(FormatPart::Arg(s)),
415 None => {
416 return Err(crate::error::SigilStitchError::InvalidFormatSpecifier {
417 format: format.to_string(),
418 specifier: spec,
419 });
420 }
421 },
422 };
423 if let Some(part) = part {
424 if !current_literal.is_empty() {
425 parts.push(FormatPart::Literal(std::mem::take(&mut current_literal)));
426 }
427 parts.push(part);
428 }
429 }
430 } else if ch == '\n' {
431 chars.next();
432 if !current_literal.is_empty() {
433 parts.push(FormatPart::Literal(std::mem::take(&mut current_literal)));
434 }
435 parts.push(FormatPart::Newline);
436 } else {
437 chars.next();
438 current_literal.push(ch);
439 }
440 }
441
442 if !current_literal.is_empty() {
443 parts.push(FormatPart::Literal(current_literal));
444 }
445
446 Ok(parts)
447}
448
449pub trait IntoArgs {
458 fn into_args(self) -> Vec<Arg>;
460}
461
462impl IntoArgs for () {
464 fn into_args(self) -> Vec<Arg> {
465 Vec::new()
466 }
467}
468
469impl IntoArgs for TypeName {
471 fn into_args(self) -> Vec<Arg> {
472 vec![Arg::TypeName(self)]
473 }
474}
475
476impl IntoArgs for &str {
478 fn into_args(self) -> Vec<Arg> {
479 vec![Arg::Literal(self.to_string())]
480 }
481}
482
483impl IntoArgs for String {
484 fn into_args(self) -> Vec<Arg> {
485 vec![Arg::Literal(self)]
486 }
487}
488
489impl IntoArgs for CodeBlock {
491 fn into_args(self) -> Vec<Arg> {
492 vec![Arg::Code(self)]
493 }
494}
495
496impl IntoArgs for Vec<Arg> {
498 fn into_args(self) -> Vec<Arg> {
499 self
500 }
501}
502
503pub struct NameArg(pub String);
519
520impl IntoArgs for NameArg {
521 fn into_args(self) -> Vec<Arg> {
522 vec![Arg::Name(self.0)]
523 }
524}
525
526pub struct StringLitArg(pub String);
542
543impl IntoArgs for StringLitArg {
544 fn into_args(self) -> Vec<Arg> {
545 vec![Arg::StringLit(self.0)]
546 }
547}
548
549impl From<TypeName> for Arg {
551 fn from(tn: TypeName) -> Self {
552 Arg::TypeName(tn)
553 }
554}
555
556impl From<&str> for Arg {
557 fn from(s: &str) -> Self {
558 Arg::Literal(s.to_string())
559 }
560}
561
562impl From<String> for Arg {
563 fn from(s: String) -> Self {
564 Arg::Literal(s)
565 }
566}
567
568impl From<CodeBlock> for Arg {
569 fn from(cb: CodeBlock) -> Self {
570 Arg::Code(cb)
571 }
572}
573
574impl From<NameArg> for Arg {
575 fn from(n: NameArg) -> Self {
576 Arg::Name(n.0)
577 }
578}
579
580impl From<StringLitArg> for Arg {
581 fn from(s: StringLitArg) -> Self {
582 Arg::StringLit(s.0)
583 }
584}
585
586macro_rules! impl_into_args_tuple {
590 ($($idx:tt $T:ident),+) => {
591 impl<$($T: Into<Arg>),+> IntoArgs for ($($T,)+) {
592 fn into_args(self) -> Vec<Arg> {
593 vec![$(self.$idx.into()),+]
594 }
595 }
596 };
597}
598
599impl_into_args_tuple!(0 A);
600impl_into_args_tuple!(0 A, 1 B);
601impl_into_args_tuple!(0 A, 1 B, 2 C);
602impl_into_args_tuple!(0 A, 1 B, 2 C, 3 D);
603impl_into_args_tuple!(0 A, 1 B, 2 C, 3 D, 4 E);
604impl_into_args_tuple!(0 A, 1 B, 2 C, 3 D, 4 E, 5 F);
605impl_into_args_tuple!(0 A, 1 B, 2 C, 3 D, 4 E, 5 F, 6 G);
606impl_into_args_tuple!(0 A, 1 B, 2 C, 3 D, 4 E, 5 F, 6 G, 7 H);
607
608#[cfg(test)]
609mod tests {
610 use super::*;
611 use crate::code_node::CodeNode;
612
613 #[test]
614 fn test_parse_all_specifiers() {
615 let parts = parse_format("hello %T world %N %S %L %W %> %< %[ %]").unwrap();
616 assert!(parts.contains(&FormatPart::Arg(Specifier::Type)));
617 assert!(parts.contains(&FormatPart::Arg(Specifier::Name)));
618 assert!(parts.contains(&FormatPart::Arg(Specifier::StringLit)));
619 assert!(parts.contains(&FormatPart::Arg(Specifier::Literal)));
620 assert!(parts.contains(&FormatPart::Wrap));
621 assert!(parts.contains(&FormatPart::Indent));
622 assert!(parts.contains(&FormatPart::Dedent));
623 assert!(parts.contains(&FormatPart::StatementBegin));
624 assert!(parts.contains(&FormatPart::StatementEnd));
625 }
626
627 #[test]
628 fn test_parse_literal_percent() {
629 let parts = parse_format("100%%").unwrap();
630 assert_eq!(parts, vec![FormatPart::Literal("100%".to_string())]);
631 }
632
633 #[test]
634 fn test_parse_empty() {
635 let parts = parse_format("").unwrap();
636 assert!(parts.is_empty());
637 }
638
639 #[test]
640 fn test_parse_newlines() {
641 let parts = parse_format("line1\nline2").unwrap();
642 assert_eq!(
643 parts,
644 vec![
645 FormatPart::Literal("line1".to_string()),
646 FormatPart::Newline,
647 FormatPart::Literal("line2".to_string()),
648 ]
649 );
650 }
651
652 #[test]
653 fn test_builder_add_statement() {
654 let mut b = CodeBlock::builder();
655 b.add_statement("const x = %L", "42");
656 let block = b.build().unwrap();
657
658 assert!(!block.is_empty());
659 let has_stmt_begin = block
660 .nodes
661 .iter()
662 .any(|n| matches!(n, CodeNode::StatementBegin));
663 let has_stmt_end = block
664 .nodes
665 .iter()
666 .any(|n| matches!(n, CodeNode::StatementEnd));
667 assert!(has_stmt_begin);
668 assert!(has_stmt_end);
669 }
670
671 #[test]
672 fn test_builder_control_flow() {
673 let mut b = CodeBlock::builder();
674 b.begin_control_flow("if (x > 0)", ());
675 b.add_statement("return x", ());
676 b.end_control_flow();
677 let block = b.build().unwrap();
678
679 assert!(!block.is_empty());
680 }
681
682 #[test]
683 fn test_builder_unbalanced_control_flow() {
684 let mut b = CodeBlock::builder();
685 b.begin_control_flow("if (x)", ());
686 b.add_statement("y()", ());
687 let result = b.build();
689 assert!(result.is_err());
690 assert!(result.unwrap_err().to_string().contains("unbalanced"));
691 }
692
693 #[test]
694 fn test_mismatched_arg_count() {
695 let mut b = CodeBlock::builder();
696 b.add("%T", ());
697 let result = b.build();
698 assert!(result.is_err());
699 assert!(
700 result
701 .unwrap_err()
702 .to_string()
703 .contains("expects 1 args but got 0")
704 );
705 }
706
707 #[test]
708 fn test_into_args_tuple() {
709 let user = TypeName::importable("./models", "User");
710 let args: Vec<Arg> = (user, "hello").into_args();
711 assert_eq!(args.len(), 2);
712 assert!(matches!(&args[0], Arg::TypeName(_)));
713 assert!(matches!(&args[1], Arg::Literal(s) if s == "hello"));
714 }
715
716 #[test]
717 fn test_into_args_single_typename() {
718 let user = TypeName::importable("./models", "User");
719 let args: Vec<Arg> = user.into_args();
720 assert_eq!(args.len(), 1);
721 }
722
723 #[test]
724 fn test_into_args_single_str() {
725 let args: Vec<Arg> = "hello".into_args();
726 assert_eq!(args.len(), 1);
727 assert!(matches!(&args[0], Arg::Literal(s) if s == "hello"));
728 }
729
730 #[test]
731 fn test_collect_imports_from_codeblock() {
732 let user = TypeName::importable("./models", "User");
733 let tag = TypeName::importable("./models", "Tag");
734 let mut b = CodeBlock::builder();
735 b.add_statement("const u: %T = getUser()", (user,));
736 b.add_statement("const t: %T = getTag()", (tag,));
737 let block = b.build().unwrap();
738
739 let mut imports = Vec::new();
740 block.collect_imports(&mut imports);
741 assert_eq!(imports.len(), 2);
742 assert_eq!(imports[0].name, "User");
743 assert_eq!(imports[1].name, "Tag");
744 }
745
746 #[test]
747 fn test_nested_codeblock_imports() {
748 let user = TypeName::importable("./models", "User");
749 let mut ib = CodeBlock::builder();
750 ib.add_statement("return new %T()", (user,));
751 let inner = ib.build().unwrap();
752
753 let mut ob = CodeBlock::builder();
754 ob.add_code(inner);
755 let outer = ob.build().unwrap();
756
757 let mut imports = Vec::new();
758 outer.collect_imports(&mut imports);
759 assert_eq!(imports.len(), 1);
760 assert_eq!(imports[0].name, "User");
761 }
762
763 #[test]
764 fn test_name_arg() {
765 let mut b = CodeBlock::builder();
766 b.add("this.%N()", (NameArg("getUser".to_string()),));
767 let block = b.build().unwrap();
768 let has_name = block
769 .nodes
770 .iter()
771 .any(|n| matches!(n, CodeNode::NameRef(s) if s == "getUser"));
772 assert!(has_name);
773 }
774
775 #[test]
776 fn test_string_lit_arg() {
777 let mut b = CodeBlock::builder();
778 b.add("const x = %S", (StringLitArg("hello".to_string()),));
779 let block = b.build().unwrap();
780 let has_str_lit = block
781 .nodes
782 .iter()
783 .any(|n| matches!(n, CodeNode::StringLit(s) if s == "hello"));
784 assert!(has_str_lit);
785 }
786
787 #[test]
788 fn test_invalid_format_specifier() {
789 let mut b = CodeBlock::builder();
790 b.add("hello %X world", ());
791 let result = b.build();
792 assert!(result.is_err());
793 let err_msg = result.unwrap_err().to_string();
794 assert!(err_msg.contains("invalid format specifier"));
795 assert!(err_msg.contains("%X"));
796 }
797
798 #[test]
799 fn test_parse_format_invalid_specifier_returns_error() {
800 let result = parse_format("foo %Z bar");
801 assert!(result.is_err());
802 let err_msg = result.unwrap_err().to_string();
803 assert!(err_msg.contains("invalid format specifier"));
804 assert!(err_msg.contains("%Z"));
805 }
806
807 #[test]
808 fn test_mismatched_arg_count_includes_specifiers_and_kinds() {
809 let user = TypeName::importable("./models", "User");
810 let mut b = CodeBlock::builder();
811 b.add("%T %S %L", (user,));
812 let result = b.build();
813 assert!(result.is_err());
814 let err_msg = result.unwrap_err().to_string();
815 assert!(err_msg.contains("expects 3 args but got 1"));
816 assert!(err_msg.contains("%T"));
817 assert!(err_msg.contains("%S"));
818 assert!(err_msg.contains("%L"));
819 assert!(err_msg.contains("TypeName"));
820 }
821
822 #[test]
823 fn test_begin_control_flow_with_open_non_empty() {
824 let mut b = CodeBlock::builder();
825 b.begin_control_flow_with_open("class Functor f", (), " where");
826 b.add_statement("fmap :: (a -> b) -> f a -> f b", ());
827 b.end_control_flow();
828 let block = b.build().unwrap();
829 let has_override = block
830 .nodes
831 .iter()
832 .any(|n| matches!(n, CodeNode::BlockOpenOverride(s) if s == " where"));
833 assert!(has_override, "should contain BlockOpenOverride(\" where\")");
834 let has_block_open = block.nodes.iter().any(|n| matches!(n, CodeNode::BlockOpen));
835 assert!(
836 !has_block_open,
837 "should NOT contain BlockOpen when override is used"
838 );
839 }
840
841 #[test]
842 fn test_begin_control_flow_with_open_empty() {
843 let mut b = CodeBlock::builder();
844 b.begin_control_flow_with_open("match x with", (), "");
845 b.add("| Red -> red", ());
846 b.add_line();
847 b.end_control_flow();
848 let block = b.build().unwrap();
849 let has_override = block
850 .nodes
851 .iter()
852 .any(|n| matches!(n, CodeNode::BlockOpenOverride(_)));
853 assert!(
854 !has_override,
855 "empty custom_open should skip BlockOpenOverride"
856 );
857 let has_block_open = block.nodes.iter().any(|n| matches!(n, CodeNode::BlockOpen));
858 assert!(!has_block_open, "should NOT contain BlockOpen either");
859 }
860}