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(String),
84 BlockClose(String),
89 BranchClose(String),
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 nodes_mut(&mut self) -> &mut Vec<CodeNode> {
152 &mut self.nodes
153 }
154
155 pub fn of(format: &str, args: impl IntoArgs) -> Result<Self, crate::error::SigilStitchError> {
157 let mut builder = CodeBlockBuilder::new();
158 builder.add(format, args);
159 builder.build()
160 }
161
162 pub fn is_empty(&self) -> bool {
164 self.nodes.is_empty()
165 }
166
167 pub fn ends_with_newline_or_block_close(&self) -> bool {
169 fn check_last(nodes: &[CodeNode]) -> bool {
170 match nodes.last() {
171 Some(CodeNode::Newline | CodeNode::BlockClose(_)) => true,
172 Some(CodeNode::Sequence(children)) => check_last(children),
173 Some(CodeNode::Nested(inner)) => check_last(&inner.nodes),
174 _ => false,
175 }
176 }
177 check_last(&self.nodes)
178 }
179
180 pub fn collect_imports(&self, out: &mut Vec<ImportRef>) {
182 crate::import_collector::walk_nodes(&self.nodes, out);
183 }
184
185 pub fn render_standalone(
191 &self,
192 lang: &dyn CodeLang,
193 width: usize,
194 ) -> Result<String, crate::error::SigilStitchError> {
195 let imports = crate::import::ImportGroup::new();
196 let mut renderer = crate::code_renderer::CodeRenderer::new(lang, &imports, width);
197 renderer.render(self)
198 }
199}
200
201#[derive(Debug)]
223pub struct CodeBlockBuilder {
224 nodes: Vec<CodeNode>,
225 indent_depth: i32,
226 block_stack: Vec<String>,
227 errors: Vec<crate::error::SigilStitchError>,
228}
229
230impl CodeBlockBuilder {
231 pub fn new() -> Self {
233 Self {
234 nodes: Vec::new(),
235 indent_depth: 0,
236 block_stack: Vec::new(),
237 errors: Vec::new(),
238 }
239 }
240
241 pub fn add(&mut self, format: &str, args: impl IntoArgs) -> &mut Self {
243 let arg_vec = args.into_args();
244 let parsed = match parse_format(format) {
245 Ok(parts) => parts,
246 Err(err) => {
247 self.errors.push(err);
248 return self;
249 }
250 };
251
252 let consuming_specifiers: Vec<String> = parsed
253 .iter()
254 .filter_map(|p| match p {
255 FormatPart::Arg(s) => Some(format!("%{}", s.format_char())),
256 _ => None,
257 })
258 .collect();
259
260 let expected_args = consuming_specifiers.len();
261
262 if expected_args != arg_vec.len() {
263 let actual_arg_kinds: Vec<String> = arg_vec
264 .iter()
265 .map(|a| match a {
266 Arg::TypeName(_) => "TypeName".to_string(),
267 Arg::Name(_) => "Name".to_string(),
268 Arg::StringLit(_) => "StringLit".to_string(),
269 Arg::Literal(_) => "Literal".to_string(),
270 Arg::Code(_) => "Code".to_string(),
271 })
272 .collect();
273 self.errors
274 .push(crate::error::SigilStitchError::FormatArgCount {
275 format: format.to_string(),
276 expected: expected_args,
277 actual: arg_vec.len(),
278 expected_specifiers: consuming_specifiers,
279 actual_arg_kinds,
280 });
281 return self;
282 }
283
284 let new_nodes = parts_args_to_nodes(&parsed, &arg_vec);
285 self.nodes.extend(new_nodes);
286 self
287 }
288
289 pub fn add_statement(&mut self, format: &str, args: impl IntoArgs) -> &mut Self {
291 self.nodes.push(CodeNode::StatementBegin);
292 self.add(format, args);
293 self.nodes.push(CodeNode::StatementEnd);
294 self.nodes.push(CodeNode::Newline);
295 self
296 }
297
298 pub fn begin_control_flow(&mut self, format: &str, args: impl IntoArgs) -> &mut Self {
310 let condition = format.to_string();
311 self.block_stack.push(condition.clone());
312 self.add(format, args);
313 self.nodes.push(CodeNode::BlockOpen(condition));
314 self.nodes.push(CodeNode::Newline);
315 self.nodes.push(CodeNode::Indent);
316 self.indent_depth += 1;
317 self
318 }
319
320 pub fn next_control_flow(&mut self, format: &str, args: impl IntoArgs) -> &mut Self {
322 let condition = self.block_stack.last().cloned().unwrap_or_default();
323 self.nodes.push(CodeNode::Dedent);
324 self.indent_depth -= 1;
325 self.nodes.push(CodeNode::BranchClose(condition));
326 self.add(format, args);
327 let new_condition = format.to_string();
328 self.nodes.push(CodeNode::BlockOpen(new_condition));
329 self.nodes.push(CodeNode::Newline);
330 self.nodes.push(CodeNode::Indent);
331 self.indent_depth += 1;
332 self
333 }
334
335 pub fn end_control_flow(&mut self) -> &mut Self {
337 let condition = self.block_stack.pop().unwrap_or_default();
338 self.nodes.push(CodeNode::Dedent);
339 self.indent_depth -= 1;
340 self.nodes.push(CodeNode::BlockClose(condition));
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_stores_condition() {
824 let mut b = CodeBlock::builder();
825 b.begin_control_flow("class Functor f", ());
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_open = block
830 .nodes
831 .iter()
832 .any(|n| matches!(n, CodeNode::BlockOpen(s) if s == "class Functor f"));
833 assert!(has_open, "should contain BlockOpen with condition text");
834 let has_close = block
835 .nodes
836 .iter()
837 .any(|n| matches!(n, CodeNode::BlockClose(s) if s == "class Functor f"));
838 assert!(has_close, "should contain BlockClose with condition text");
839 }
840
841 #[test]
842 fn test_begin_control_flow_match_empty_open() {
843 let mut b = CodeBlock::builder();
844 b.begin_control_flow("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_open = block
850 .nodes
851 .iter()
852 .any(|n| matches!(n, CodeNode::BlockOpen(s) if s == "match x with"));
853 assert!(has_open, "should contain BlockOpen(\"match x with\")");
854 }
855}