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 VerbatimStr,
28 Literal,
30 Comment,
32}
33
34impl Specifier {
35 pub fn from_format_char(ch: char) -> Option<Self> {
40 match ch {
41 'T' => Some(Self::Type),
42 'N' => Some(Self::Name),
43 'S' => Some(Self::StringLit),
44 'V' => Some(Self::VerbatimStr),
45 'L' => Some(Self::Literal),
46 'R' => Some(Self::Comment),
47 _ => None,
48 }
49 }
50
51 pub fn format_char(self) -> char {
53 match self {
54 Self::Type => 'T',
55 Self::Name => 'N',
56 Self::StringLit => 'S',
57 Self::VerbatimStr => 'V',
58 Self::Literal => 'L',
59 Self::Comment => 'R',
60 }
61 }
62
63 pub fn all() -> &'static [Self] {
65 &[
66 Self::Type,
67 Self::Name,
68 Self::StringLit,
69 Self::VerbatimStr,
70 Self::Literal,
71 Self::Comment,
72 ]
73 }
74}
75
76#[derive(Debug, Clone, PartialEq, serde::Serialize, serde::Deserialize)]
78pub(crate) enum FormatPart {
79 Literal(String),
81 Arg(Specifier),
83 Wrap,
85 Indent,
87 Dedent,
89 StatementBegin,
91 StatementEnd,
93 Newline,
95 BlockOpen(String),
100 BlockClose(String),
105 BranchClose(String),
110}
111
112#[derive(Debug, Clone, serde::Serialize, serde::Deserialize)]
114pub enum Arg {
115 TypeName(TypeName),
117 Name(String),
119 StringLit(String),
121 VerbatimStr(String),
123 Literal(String),
125 Code(CodeBlock),
127 Comment(String),
129}
130
131#[derive(Debug, Clone, serde::Serialize, serde::Deserialize)]
160pub struct CodeBlock {
161 pub(crate) nodes: Vec<CodeNode>,
162}
163
164impl CodeBlock {
165 pub fn builder() -> CodeBlockBuilder {
167 CodeBlockBuilder::new()
168 }
169
170 pub fn nodes_mut(&mut self) -> &mut Vec<CodeNode> {
172 &mut self.nodes
173 }
174
175 pub fn of(format: &str, args: impl IntoArgs) -> Result<Self, crate::error::SigilStitchError> {
177 let mut builder = CodeBlockBuilder::new();
178 builder.add(format, args);
179 builder.build()
180 }
181
182 pub fn is_empty(&self) -> bool {
184 self.nodes.is_empty()
185 }
186
187 pub fn ends_with_newline_or_block_close(&self) -> bool {
189 fn check_last(nodes: &[CodeNode]) -> bool {
190 match nodes.last() {
191 Some(CodeNode::Newline | CodeNode::BlockClose(_)) => true,
192 Some(CodeNode::Sequence(children)) => check_last(children),
193 Some(CodeNode::Nested(inner)) => check_last(&inner.nodes),
194 _ => false,
195 }
196 }
197 check_last(&self.nodes)
198 }
199
200 pub fn collect_imports(&self, out: &mut Vec<ImportRef>) {
202 crate::import_collector::walk_nodes(&self.nodes, out);
203 }
204
205 pub fn render_standalone(
211 &self,
212 lang: &dyn CodeLang,
213 width: usize,
214 ) -> Result<String, crate::error::SigilStitchError> {
215 let imports = crate::import::ImportGroup::new();
216 let mut renderer = crate::code_renderer::CodeRenderer::new(lang, &imports, width);
217 renderer.render(self)
218 }
219}
220
221#[derive(Debug)]
243pub struct CodeBlockBuilder {
244 nodes: Vec<CodeNode>,
245 indent_depth: i32,
246 block_stack: Vec<String>,
247 errors: Vec<crate::error::SigilStitchError>,
248}
249
250impl CodeBlockBuilder {
251 pub fn new() -> Self {
253 Self {
254 nodes: Vec::new(),
255 indent_depth: 0,
256 block_stack: Vec::new(),
257 errors: Vec::new(),
258 }
259 }
260
261 pub fn add(&mut self, format: &str, args: impl IntoArgs) -> &mut Self {
263 let arg_vec = args.into_args();
264 let parsed = match parse_format(format) {
265 Ok(parts) => parts,
266 Err(err) => {
267 self.errors.push(err);
268 return self;
269 }
270 };
271
272 let consuming_specifiers: Vec<String> = parsed
273 .iter()
274 .filter_map(|p| match p {
275 FormatPart::Arg(s) => Some(format!("%{}", s.format_char())),
276 _ => None,
277 })
278 .collect();
279
280 let expected_args = consuming_specifiers.len();
281
282 if expected_args != arg_vec.len() {
283 let actual_arg_kinds: Vec<String> = arg_vec
284 .iter()
285 .map(|a| match a {
286 Arg::TypeName(_) => "TypeName".to_string(),
287 Arg::Name(_) => "Name".to_string(),
288 Arg::StringLit(_) => "StringLit".to_string(),
289 Arg::VerbatimStr(_) => "VerbatimStr".to_string(),
290 Arg::Literal(_) => "Literal".to_string(),
291 Arg::Code(_) => "Code".to_string(),
292 Arg::Comment(_) => "Comment".to_string(),
293 })
294 .collect();
295 self.errors
296 .push(crate::error::SigilStitchError::FormatArgCount {
297 format: format.to_string(),
298 expected: expected_args,
299 actual: arg_vec.len(),
300 expected_specifiers: consuming_specifiers,
301 actual_arg_kinds,
302 });
303 return self;
304 }
305
306 let new_nodes = parts_args_to_nodes(&parsed, &arg_vec);
307 self.nodes.extend(new_nodes);
308 self
309 }
310
311 pub fn add_statement(&mut self, format: &str, args: impl IntoArgs) -> &mut Self {
313 self.nodes.push(CodeNode::StatementBegin);
314 self.add(format, args);
315 self.nodes.push(CodeNode::StatementEnd);
316 self.nodes.push(CodeNode::Newline);
317 self
318 }
319
320 pub fn begin_control_flow(&mut self, format: &str, args: impl IntoArgs) -> &mut Self {
332 let condition = format.to_string();
333 self.block_stack.push(condition.clone());
334 self.add(format, args);
335 self.nodes.push(CodeNode::BlockOpen(condition));
336 self.nodes.push(CodeNode::Newline);
337 self.nodes.push(CodeNode::Indent);
338 self.indent_depth += 1;
339 self
340 }
341
342 pub fn next_control_flow(&mut self, format: &str, args: impl IntoArgs) -> &mut Self {
344 let condition = self.block_stack.last().cloned().unwrap_or_default();
345 self.nodes.push(CodeNode::Dedent);
346 self.indent_depth -= 1;
347 self.nodes.push(CodeNode::BranchClose(condition));
348 self.add(format, args);
349 let new_condition = format.to_string();
350 self.nodes.push(CodeNode::BlockOpen(new_condition));
351 self.nodes.push(CodeNode::Newline);
352 self.nodes.push(CodeNode::Indent);
353 self.indent_depth += 1;
354 self
355 }
356
357 pub fn end_control_flow(&mut self) -> &mut Self {
360 let condition = self.block_stack.pop().unwrap_or_default();
361 self.nodes.push(CodeNode::Dedent);
362 self.indent_depth -= 1;
363 self.nodes.push(CodeNode::BlockClose(condition));
364 self.nodes.push(CodeNode::Newline);
365 self
366 }
367
368 pub fn end_control_flow_no_newline(&mut self) -> &mut Self {
375 let condition = self.block_stack.pop().unwrap_or_default();
376 self.nodes.push(CodeNode::Dedent);
377 self.indent_depth -= 1;
378 self.nodes.push(CodeNode::BlockClose(condition));
379 self
380 }
381
382 pub fn end_control_flow_with_semicolon(&mut self) -> &mut Self {
385 let condition = self.block_stack.pop().unwrap_or_default();
386 self.nodes.push(CodeNode::Dedent);
387 self.indent_depth -= 1;
388 self.nodes.push(CodeNode::BlockClose(condition));
389 self.nodes.push(CodeNode::StatementEnd);
390 self.nodes.push(CodeNode::Newline);
391 self
392 }
393
394 pub fn add_line(&mut self) -> &mut Self {
396 self.nodes.push(CodeNode::Newline);
397 self
398 }
399
400 pub fn add_comment(&mut self, text: &str) -> &mut Self {
402 self.nodes.push(CodeNode::Comment(text.to_string()));
403 self.nodes.push(CodeNode::Newline);
404 self
405 }
406
407 pub fn add_attribute(&mut self, text: &str) -> &mut Self {
412 self.nodes.push(CodeNode::Attribute(text.to_string()));
413 self.nodes.push(CodeNode::Newline);
414 self
415 }
416
417 pub fn add_code(&mut self, block: CodeBlock) -> &mut Self {
419 self.nodes.push(CodeNode::Nested(block));
420 self
421 }
422
423 pub fn build(self) -> Result<CodeBlock, crate::error::SigilStitchError> {
429 if let Some(err) = self.errors.into_iter().next() {
430 return Err(err);
431 }
432 if self.indent_depth != 0 {
433 return Err(crate::error::SigilStitchError::UnbalancedIndent {
434 depth: self.indent_depth,
435 });
436 }
437 Ok(CodeBlock { nodes: self.nodes })
438 }
439
440 pub fn build_unwrap(self) -> CodeBlock {
442 self.build().unwrap()
443 }
444}
445
446impl Default for CodeBlockBuilder {
447 fn default() -> Self {
448 Self::new()
449 }
450}
451
452fn parse_format(format: &str) -> Result<Vec<FormatPart>, crate::error::SigilStitchError> {
454 let mut parts = Vec::new();
455 let mut current_literal = String::new();
456 let mut chars = format.char_indices().peekable();
457
458 while let Some(&(_, ch)) = chars.peek() {
459 if ch == '%' {
460 chars.next();
461 if let Some(&(_, spec)) = chars.peek() {
462 chars.next();
463 let part = match spec {
464 'W' => Some(FormatPart::Wrap),
465 '>' => Some(FormatPart::Indent),
466 '<' => Some(FormatPart::Dedent),
467 '[' => Some(FormatPart::StatementBegin),
468 ']' => Some(FormatPart::StatementEnd),
469 '%' => {
470 current_literal.push('%');
471 continue;
472 }
473 _ => match Specifier::from_format_char(spec) {
474 Some(s) => Some(FormatPart::Arg(s)),
475 None => {
476 return Err(crate::error::SigilStitchError::InvalidFormatSpecifier {
477 format: format.to_string(),
478 specifier: spec,
479 });
480 }
481 },
482 };
483 if let Some(part) = part {
484 if !current_literal.is_empty() {
485 parts.push(FormatPart::Literal(std::mem::take(&mut current_literal)));
486 }
487 parts.push(part);
488 }
489 }
490 } else if ch == '\n' {
491 chars.next();
492 if !current_literal.is_empty() {
493 parts.push(FormatPart::Literal(std::mem::take(&mut current_literal)));
494 }
495 parts.push(FormatPart::Newline);
496 } else {
497 chars.next();
498 current_literal.push(ch);
499 }
500 }
501
502 if !current_literal.is_empty() {
503 parts.push(FormatPart::Literal(current_literal));
504 }
505
506 Ok(parts)
507}
508
509pub trait IntoArgs {
518 fn into_args(self) -> Vec<Arg>;
520}
521
522impl IntoArgs for () {
524 fn into_args(self) -> Vec<Arg> {
525 Vec::new()
526 }
527}
528
529impl IntoArgs for TypeName {
531 fn into_args(self) -> Vec<Arg> {
532 vec![Arg::TypeName(self)]
533 }
534}
535
536impl IntoArgs for &str {
538 fn into_args(self) -> Vec<Arg> {
539 vec![Arg::Literal(self.to_string())]
540 }
541}
542
543impl IntoArgs for String {
544 fn into_args(self) -> Vec<Arg> {
545 vec![Arg::Literal(self)]
546 }
547}
548
549impl IntoArgs for CodeBlock {
551 fn into_args(self) -> Vec<Arg> {
552 vec![Arg::Code(self)]
553 }
554}
555
556impl IntoArgs for Vec<Arg> {
558 fn into_args(self) -> Vec<Arg> {
559 self
560 }
561}
562
563pub struct NameArg(pub String);
579
580impl IntoArgs for NameArg {
581 fn into_args(self) -> Vec<Arg> {
582 vec![Arg::Name(self.0)]
583 }
584}
585
586pub struct StringLitArg(pub String);
602
603impl IntoArgs for StringLitArg {
604 fn into_args(self) -> Vec<Arg> {
605 vec![Arg::StringLit(self.0)]
606 }
607}
608
609pub struct VerbatimStrArg(pub String);
622
623impl IntoArgs for VerbatimStrArg {
624 fn into_args(self) -> Vec<Arg> {
625 vec![Arg::VerbatimStr(self.0)]
626 }
627}
628
629pub struct CommentArg(pub String);
644
645impl IntoArgs for CommentArg {
646 fn into_args(self) -> Vec<Arg> {
647 vec![Arg::Comment(self.0)]
648 }
649}
650
651impl From<TypeName> for Arg {
653 fn from(tn: TypeName) -> Self {
654 Arg::TypeName(tn)
655 }
656}
657
658impl From<&str> for Arg {
659 fn from(s: &str) -> Self {
660 Arg::Literal(s.to_string())
661 }
662}
663
664impl From<String> for Arg {
665 fn from(s: String) -> Self {
666 Arg::Literal(s)
667 }
668}
669
670impl From<CodeBlock> for Arg {
671 fn from(cb: CodeBlock) -> Self {
672 Arg::Code(cb)
673 }
674}
675
676impl From<NameArg> for Arg {
677 fn from(n: NameArg) -> Self {
678 Arg::Name(n.0)
679 }
680}
681
682impl From<StringLitArg> for Arg {
683 fn from(s: StringLitArg) -> Self {
684 Arg::StringLit(s.0)
685 }
686}
687
688impl From<VerbatimStrArg> for Arg {
689 fn from(s: VerbatimStrArg) -> Self {
690 Arg::VerbatimStr(s.0)
691 }
692}
693
694impl From<CommentArg> for Arg {
695 fn from(s: CommentArg) -> Self {
696 Arg::Comment(s.0)
697 }
698}
699
700macro_rules! impl_into_args_tuple {
704 ($($idx:tt $T:ident),+) => {
705 impl<$($T: Into<Arg>),+> IntoArgs for ($($T,)+) {
706 fn into_args(self) -> Vec<Arg> {
707 vec![$(self.$idx.into()),+]
708 }
709 }
710 };
711}
712
713impl_into_args_tuple!(0 A);
714impl_into_args_tuple!(0 A, 1 B);
715impl_into_args_tuple!(0 A, 1 B, 2 C);
716impl_into_args_tuple!(0 A, 1 B, 2 C, 3 D);
717impl_into_args_tuple!(0 A, 1 B, 2 C, 3 D, 4 E);
718impl_into_args_tuple!(0 A, 1 B, 2 C, 3 D, 4 E, 5 F);
719impl_into_args_tuple!(0 A, 1 B, 2 C, 3 D, 4 E, 5 F, 6 G);
720impl_into_args_tuple!(0 A, 1 B, 2 C, 3 D, 4 E, 5 F, 6 G, 7 H);
721
722#[cfg(test)]
723mod tests {
724 use super::*;
725 use crate::code_node::CodeNode;
726
727 #[test]
728 fn test_parse_all_specifiers() {
729 let parts = parse_format("hello %T world %N %S %L %W %> %< %[ %]").unwrap();
730 assert!(parts.contains(&FormatPart::Arg(Specifier::Type)));
731 assert!(parts.contains(&FormatPart::Arg(Specifier::Name)));
732 assert!(parts.contains(&FormatPart::Arg(Specifier::StringLit)));
733 assert!(parts.contains(&FormatPart::Arg(Specifier::Literal)));
734 assert!(parts.contains(&FormatPart::Wrap));
735 assert!(parts.contains(&FormatPart::Indent));
736 assert!(parts.contains(&FormatPart::Dedent));
737 assert!(parts.contains(&FormatPart::StatementBegin));
738 assert!(parts.contains(&FormatPart::StatementEnd));
739 }
740
741 #[test]
742 fn test_parse_literal_percent() {
743 let parts = parse_format("100%%").unwrap();
744 assert_eq!(parts, vec![FormatPart::Literal("100%".to_string())]);
745 }
746
747 #[test]
748 fn test_parse_empty() {
749 let parts = parse_format("").unwrap();
750 assert!(parts.is_empty());
751 }
752
753 #[test]
754 fn test_parse_newlines() {
755 let parts = parse_format("line1\nline2").unwrap();
756 assert_eq!(
757 parts,
758 vec![
759 FormatPart::Literal("line1".to_string()),
760 FormatPart::Newline,
761 FormatPart::Literal("line2".to_string()),
762 ]
763 );
764 }
765
766 #[test]
767 fn test_builder_add_statement() {
768 let mut b = CodeBlock::builder();
769 b.add_statement("const x = %L", "42");
770 let block = b.build().unwrap();
771
772 assert!(!block.is_empty());
773 let has_stmt_begin = block
774 .nodes
775 .iter()
776 .any(|n| matches!(n, CodeNode::StatementBegin));
777 let has_stmt_end = block
778 .nodes
779 .iter()
780 .any(|n| matches!(n, CodeNode::StatementEnd));
781 assert!(has_stmt_begin);
782 assert!(has_stmt_end);
783 }
784
785 #[test]
786 fn test_builder_control_flow() {
787 let mut b = CodeBlock::builder();
788 b.begin_control_flow("if (x > 0)", ());
789 b.add_statement("return x", ());
790 b.end_control_flow();
791 let block = b.build().unwrap();
792
793 assert!(!block.is_empty());
794 }
795
796 #[test]
797 fn test_builder_unbalanced_control_flow() {
798 let mut b = CodeBlock::builder();
799 b.begin_control_flow("if (x)", ());
800 b.add_statement("y()", ());
801 let result = b.build();
803 assert!(result.is_err());
804 assert!(result.unwrap_err().to_string().contains("unbalanced"));
805 }
806
807 #[test]
808 fn test_mismatched_arg_count() {
809 let mut b = CodeBlock::builder();
810 b.add("%T", ());
811 let result = b.build();
812 assert!(result.is_err());
813 assert!(
814 result
815 .unwrap_err()
816 .to_string()
817 .contains("expects 1 args but got 0")
818 );
819 }
820
821 #[test]
822 fn test_into_args_tuple() {
823 let user = TypeName::importable("./models", "User");
824 let args: Vec<Arg> = (user, "hello").into_args();
825 assert_eq!(args.len(), 2);
826 assert!(matches!(&args[0], Arg::TypeName(_)));
827 assert!(matches!(&args[1], Arg::Literal(s) if s == "hello"));
828 }
829
830 #[test]
831 fn test_into_args_single_typename() {
832 let user = TypeName::importable("./models", "User");
833 let args: Vec<Arg> = user.into_args();
834 assert_eq!(args.len(), 1);
835 }
836
837 #[test]
838 fn test_into_args_single_str() {
839 let args: Vec<Arg> = "hello".into_args();
840 assert_eq!(args.len(), 1);
841 assert!(matches!(&args[0], Arg::Literal(s) if s == "hello"));
842 }
843
844 #[test]
845 fn test_collect_imports_from_codeblock() {
846 let user = TypeName::importable("./models", "User");
847 let tag = TypeName::importable("./models", "Tag");
848 let mut b = CodeBlock::builder();
849 b.add_statement("const u: %T = getUser()", (user,));
850 b.add_statement("const t: %T = getTag()", (tag,));
851 let block = b.build().unwrap();
852
853 let mut imports = Vec::new();
854 block.collect_imports(&mut imports);
855 assert_eq!(imports.len(), 2);
856 assert_eq!(imports[0].name, "User");
857 assert_eq!(imports[1].name, "Tag");
858 }
859
860 #[test]
861 fn test_nested_codeblock_imports() {
862 let user = TypeName::importable("./models", "User");
863 let mut ib = CodeBlock::builder();
864 ib.add_statement("return new %T()", (user,));
865 let inner = ib.build().unwrap();
866
867 let mut ob = CodeBlock::builder();
868 ob.add_code(inner);
869 let outer = ob.build().unwrap();
870
871 let mut imports = Vec::new();
872 outer.collect_imports(&mut imports);
873 assert_eq!(imports.len(), 1);
874 assert_eq!(imports[0].name, "User");
875 }
876
877 #[test]
878 fn test_name_arg() {
879 let mut b = CodeBlock::builder();
880 b.add("this.%N()", (NameArg("getUser".to_string()),));
881 let block = b.build().unwrap();
882 let has_name = block
883 .nodes
884 .iter()
885 .any(|n| matches!(n, CodeNode::NameRef(s) if s == "getUser"));
886 assert!(has_name);
887 }
888
889 #[test]
890 fn test_string_lit_arg() {
891 let mut b = CodeBlock::builder();
892 b.add("const x = %S", (StringLitArg("hello".to_string()),));
893 let block = b.build().unwrap();
894 let has_str_lit = block
895 .nodes
896 .iter()
897 .any(|n| matches!(n, CodeNode::StringLit(s) if s == "hello"));
898 assert!(has_str_lit);
899 }
900
901 #[test]
902 fn test_invalid_format_specifier() {
903 let mut b = CodeBlock::builder();
904 b.add("hello %X world", ());
905 let result = b.build();
906 assert!(result.is_err());
907 let err_msg = result.unwrap_err().to_string();
908 assert!(err_msg.contains("invalid format specifier"));
909 assert!(err_msg.contains("%X"));
910 }
911
912 #[test]
913 fn test_parse_format_invalid_specifier_returns_error() {
914 let result = parse_format("foo %Z bar");
915 assert!(result.is_err());
916 let err_msg = result.unwrap_err().to_string();
917 assert!(err_msg.contains("invalid format specifier"));
918 assert!(err_msg.contains("%Z"));
919 }
920
921 #[test]
922 fn test_mismatched_arg_count_includes_specifiers_and_kinds() {
923 let user = TypeName::importable("./models", "User");
924 let mut b = CodeBlock::builder();
925 b.add("%T %S %L", (user,));
926 let result = b.build();
927 assert!(result.is_err());
928 let err_msg = result.unwrap_err().to_string();
929 assert!(err_msg.contains("expects 3 args but got 1"));
930 assert!(err_msg.contains("%T"));
931 assert!(err_msg.contains("%S"));
932 assert!(err_msg.contains("%L"));
933 assert!(err_msg.contains("TypeName"));
934 }
935
936 #[test]
937 fn test_begin_control_flow_stores_condition() {
938 let mut b = CodeBlock::builder();
939 b.begin_control_flow("class Functor f", ());
940 b.add_statement("fmap :: (a -> b) -> f a -> f b", ());
941 b.end_control_flow();
942 let block = b.build().unwrap();
943 let has_open = block
944 .nodes
945 .iter()
946 .any(|n| matches!(n, CodeNode::BlockOpen(s) if s == "class Functor f"));
947 assert!(has_open, "should contain BlockOpen with condition text");
948 let has_close = block
949 .nodes
950 .iter()
951 .any(|n| matches!(n, CodeNode::BlockClose(s) if s == "class Functor f"));
952 assert!(has_close, "should contain BlockClose with condition text");
953 }
954
955 #[test]
956 fn test_begin_control_flow_match_empty_open() {
957 let mut b = CodeBlock::builder();
958 b.begin_control_flow("match x with", ());
959 b.add("| Red -> red", ());
960 b.add_line();
961 b.end_control_flow();
962 let block = b.build().unwrap();
963 let has_open = block
964 .nodes
965 .iter()
966 .any(|n| matches!(n, CodeNode::BlockOpen(s) if s == "match x with"));
967 assert!(has_open, "should contain BlockOpen(\"match x with\")");
968 }
969}