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 add_line(&mut self) -> &mut Self {
384 self.nodes.push(CodeNode::Newline);
385 self
386 }
387
388 pub fn add_comment(&mut self, text: &str) -> &mut Self {
390 self.nodes.push(CodeNode::Comment(text.to_string()));
391 self.nodes.push(CodeNode::Newline);
392 self
393 }
394
395 pub fn add_attribute(&mut self, text: &str) -> &mut Self {
400 self.nodes.push(CodeNode::Attribute(text.to_string()));
401 self.nodes.push(CodeNode::Newline);
402 self
403 }
404
405 pub fn add_code(&mut self, block: CodeBlock) -> &mut Self {
407 self.nodes.push(CodeNode::Nested(block));
408 self
409 }
410
411 pub fn build(self) -> Result<CodeBlock, crate::error::SigilStitchError> {
417 if let Some(err) = self.errors.into_iter().next() {
418 return Err(err);
419 }
420 if self.indent_depth != 0 {
421 return Err(crate::error::SigilStitchError::UnbalancedIndent {
422 depth: self.indent_depth,
423 });
424 }
425 Ok(CodeBlock { nodes: self.nodes })
426 }
427
428 pub fn build_unwrap(self) -> CodeBlock {
430 self.build().unwrap()
431 }
432}
433
434impl Default for CodeBlockBuilder {
435 fn default() -> Self {
436 Self::new()
437 }
438}
439
440fn parse_format(format: &str) -> Result<Vec<FormatPart>, crate::error::SigilStitchError> {
442 let mut parts = Vec::new();
443 let mut current_literal = String::new();
444 let mut chars = format.char_indices().peekable();
445
446 while let Some(&(_, ch)) = chars.peek() {
447 if ch == '%' {
448 chars.next();
449 if let Some(&(_, spec)) = chars.peek() {
450 chars.next();
451 let part = match spec {
452 'W' => Some(FormatPart::Wrap),
453 '>' => Some(FormatPart::Indent),
454 '<' => Some(FormatPart::Dedent),
455 '[' => Some(FormatPart::StatementBegin),
456 ']' => Some(FormatPart::StatementEnd),
457 '%' => {
458 current_literal.push('%');
459 continue;
460 }
461 _ => match Specifier::from_format_char(spec) {
462 Some(s) => Some(FormatPart::Arg(s)),
463 None => {
464 return Err(crate::error::SigilStitchError::InvalidFormatSpecifier {
465 format: format.to_string(),
466 specifier: spec,
467 });
468 }
469 },
470 };
471 if let Some(part) = part {
472 if !current_literal.is_empty() {
473 parts.push(FormatPart::Literal(std::mem::take(&mut current_literal)));
474 }
475 parts.push(part);
476 }
477 }
478 } else if ch == '\n' {
479 chars.next();
480 if !current_literal.is_empty() {
481 parts.push(FormatPart::Literal(std::mem::take(&mut current_literal)));
482 }
483 parts.push(FormatPart::Newline);
484 } else {
485 chars.next();
486 current_literal.push(ch);
487 }
488 }
489
490 if !current_literal.is_empty() {
491 parts.push(FormatPart::Literal(current_literal));
492 }
493
494 Ok(parts)
495}
496
497pub trait IntoArgs {
506 fn into_args(self) -> Vec<Arg>;
508}
509
510impl IntoArgs for () {
512 fn into_args(self) -> Vec<Arg> {
513 Vec::new()
514 }
515}
516
517impl IntoArgs for TypeName {
519 fn into_args(self) -> Vec<Arg> {
520 vec![Arg::TypeName(self)]
521 }
522}
523
524impl IntoArgs for &str {
526 fn into_args(self) -> Vec<Arg> {
527 vec![Arg::Literal(self.to_string())]
528 }
529}
530
531impl IntoArgs for String {
532 fn into_args(self) -> Vec<Arg> {
533 vec![Arg::Literal(self)]
534 }
535}
536
537impl IntoArgs for CodeBlock {
539 fn into_args(self) -> Vec<Arg> {
540 vec![Arg::Code(self)]
541 }
542}
543
544impl IntoArgs for Vec<Arg> {
546 fn into_args(self) -> Vec<Arg> {
547 self
548 }
549}
550
551pub struct NameArg(pub String);
567
568impl IntoArgs for NameArg {
569 fn into_args(self) -> Vec<Arg> {
570 vec![Arg::Name(self.0)]
571 }
572}
573
574pub struct StringLitArg(pub String);
590
591impl IntoArgs for StringLitArg {
592 fn into_args(self) -> Vec<Arg> {
593 vec![Arg::StringLit(self.0)]
594 }
595}
596
597pub struct VerbatimStrArg(pub String);
610
611impl IntoArgs for VerbatimStrArg {
612 fn into_args(self) -> Vec<Arg> {
613 vec![Arg::VerbatimStr(self.0)]
614 }
615}
616
617pub struct CommentArg(pub String);
632
633impl IntoArgs for CommentArg {
634 fn into_args(self) -> Vec<Arg> {
635 vec![Arg::Comment(self.0)]
636 }
637}
638
639impl From<TypeName> for Arg {
641 fn from(tn: TypeName) -> Self {
642 Arg::TypeName(tn)
643 }
644}
645
646impl From<&str> for Arg {
647 fn from(s: &str) -> Self {
648 Arg::Literal(s.to_string())
649 }
650}
651
652impl From<String> for Arg {
653 fn from(s: String) -> Self {
654 Arg::Literal(s)
655 }
656}
657
658impl From<CodeBlock> for Arg {
659 fn from(cb: CodeBlock) -> Self {
660 Arg::Code(cb)
661 }
662}
663
664impl From<NameArg> for Arg {
665 fn from(n: NameArg) -> Self {
666 Arg::Name(n.0)
667 }
668}
669
670impl From<StringLitArg> for Arg {
671 fn from(s: StringLitArg) -> Self {
672 Arg::StringLit(s.0)
673 }
674}
675
676impl From<VerbatimStrArg> for Arg {
677 fn from(s: VerbatimStrArg) -> Self {
678 Arg::VerbatimStr(s.0)
679 }
680}
681
682impl From<CommentArg> for Arg {
683 fn from(s: CommentArg) -> Self {
684 Arg::Comment(s.0)
685 }
686}
687
688macro_rules! impl_into_args_tuple {
692 ($($idx:tt $T:ident),+) => {
693 impl<$($T: Into<Arg>),+> IntoArgs for ($($T,)+) {
694 fn into_args(self) -> Vec<Arg> {
695 vec![$(self.$idx.into()),+]
696 }
697 }
698 };
699}
700
701impl_into_args_tuple!(0 A);
702impl_into_args_tuple!(0 A, 1 B);
703impl_into_args_tuple!(0 A, 1 B, 2 C);
704impl_into_args_tuple!(0 A, 1 B, 2 C, 3 D);
705impl_into_args_tuple!(0 A, 1 B, 2 C, 3 D, 4 E);
706impl_into_args_tuple!(0 A, 1 B, 2 C, 3 D, 4 E, 5 F);
707impl_into_args_tuple!(0 A, 1 B, 2 C, 3 D, 4 E, 5 F, 6 G);
708impl_into_args_tuple!(0 A, 1 B, 2 C, 3 D, 4 E, 5 F, 6 G, 7 H);
709
710#[cfg(test)]
711mod tests {
712 use super::*;
713 use crate::code_node::CodeNode;
714
715 #[test]
716 fn test_parse_all_specifiers() {
717 let parts = parse_format("hello %T world %N %S %L %W %> %< %[ %]").unwrap();
718 assert!(parts.contains(&FormatPart::Arg(Specifier::Type)));
719 assert!(parts.contains(&FormatPart::Arg(Specifier::Name)));
720 assert!(parts.contains(&FormatPart::Arg(Specifier::StringLit)));
721 assert!(parts.contains(&FormatPart::Arg(Specifier::Literal)));
722 assert!(parts.contains(&FormatPart::Wrap));
723 assert!(parts.contains(&FormatPart::Indent));
724 assert!(parts.contains(&FormatPart::Dedent));
725 assert!(parts.contains(&FormatPart::StatementBegin));
726 assert!(parts.contains(&FormatPart::StatementEnd));
727 }
728
729 #[test]
730 fn test_parse_literal_percent() {
731 let parts = parse_format("100%%").unwrap();
732 assert_eq!(parts, vec![FormatPart::Literal("100%".to_string())]);
733 }
734
735 #[test]
736 fn test_parse_empty() {
737 let parts = parse_format("").unwrap();
738 assert!(parts.is_empty());
739 }
740
741 #[test]
742 fn test_parse_newlines() {
743 let parts = parse_format("line1\nline2").unwrap();
744 assert_eq!(
745 parts,
746 vec![
747 FormatPart::Literal("line1".to_string()),
748 FormatPart::Newline,
749 FormatPart::Literal("line2".to_string()),
750 ]
751 );
752 }
753
754 #[test]
755 fn test_builder_add_statement() {
756 let mut b = CodeBlock::builder();
757 b.add_statement("const x = %L", "42");
758 let block = b.build().unwrap();
759
760 assert!(!block.is_empty());
761 let has_stmt_begin = block
762 .nodes
763 .iter()
764 .any(|n| matches!(n, CodeNode::StatementBegin));
765 let has_stmt_end = block
766 .nodes
767 .iter()
768 .any(|n| matches!(n, CodeNode::StatementEnd));
769 assert!(has_stmt_begin);
770 assert!(has_stmt_end);
771 }
772
773 #[test]
774 fn test_builder_control_flow() {
775 let mut b = CodeBlock::builder();
776 b.begin_control_flow("if (x > 0)", ());
777 b.add_statement("return x", ());
778 b.end_control_flow();
779 let block = b.build().unwrap();
780
781 assert!(!block.is_empty());
782 }
783
784 #[test]
785 fn test_builder_unbalanced_control_flow() {
786 let mut b = CodeBlock::builder();
787 b.begin_control_flow("if (x)", ());
788 b.add_statement("y()", ());
789 let result = b.build();
791 assert!(result.is_err());
792 assert!(result.unwrap_err().to_string().contains("unbalanced"));
793 }
794
795 #[test]
796 fn test_mismatched_arg_count() {
797 let mut b = CodeBlock::builder();
798 b.add("%T", ());
799 let result = b.build();
800 assert!(result.is_err());
801 assert!(
802 result
803 .unwrap_err()
804 .to_string()
805 .contains("expects 1 args but got 0")
806 );
807 }
808
809 #[test]
810 fn test_into_args_tuple() {
811 let user = TypeName::importable("./models", "User");
812 let args: Vec<Arg> = (user, "hello").into_args();
813 assert_eq!(args.len(), 2);
814 assert!(matches!(&args[0], Arg::TypeName(_)));
815 assert!(matches!(&args[1], Arg::Literal(s) if s == "hello"));
816 }
817
818 #[test]
819 fn test_into_args_single_typename() {
820 let user = TypeName::importable("./models", "User");
821 let args: Vec<Arg> = user.into_args();
822 assert_eq!(args.len(), 1);
823 }
824
825 #[test]
826 fn test_into_args_single_str() {
827 let args: Vec<Arg> = "hello".into_args();
828 assert_eq!(args.len(), 1);
829 assert!(matches!(&args[0], Arg::Literal(s) if s == "hello"));
830 }
831
832 #[test]
833 fn test_collect_imports_from_codeblock() {
834 let user = TypeName::importable("./models", "User");
835 let tag = TypeName::importable("./models", "Tag");
836 let mut b = CodeBlock::builder();
837 b.add_statement("const u: %T = getUser()", (user,));
838 b.add_statement("const t: %T = getTag()", (tag,));
839 let block = b.build().unwrap();
840
841 let mut imports = Vec::new();
842 block.collect_imports(&mut imports);
843 assert_eq!(imports.len(), 2);
844 assert_eq!(imports[0].name, "User");
845 assert_eq!(imports[1].name, "Tag");
846 }
847
848 #[test]
849 fn test_nested_codeblock_imports() {
850 let user = TypeName::importable("./models", "User");
851 let mut ib = CodeBlock::builder();
852 ib.add_statement("return new %T()", (user,));
853 let inner = ib.build().unwrap();
854
855 let mut ob = CodeBlock::builder();
856 ob.add_code(inner);
857 let outer = ob.build().unwrap();
858
859 let mut imports = Vec::new();
860 outer.collect_imports(&mut imports);
861 assert_eq!(imports.len(), 1);
862 assert_eq!(imports[0].name, "User");
863 }
864
865 #[test]
866 fn test_name_arg() {
867 let mut b = CodeBlock::builder();
868 b.add("this.%N()", (NameArg("getUser".to_string()),));
869 let block = b.build().unwrap();
870 let has_name = block
871 .nodes
872 .iter()
873 .any(|n| matches!(n, CodeNode::NameRef(s) if s == "getUser"));
874 assert!(has_name);
875 }
876
877 #[test]
878 fn test_string_lit_arg() {
879 let mut b = CodeBlock::builder();
880 b.add("const x = %S", (StringLitArg("hello".to_string()),));
881 let block = b.build().unwrap();
882 let has_str_lit = block
883 .nodes
884 .iter()
885 .any(|n| matches!(n, CodeNode::StringLit(s) if s == "hello"));
886 assert!(has_str_lit);
887 }
888
889 #[test]
890 fn test_invalid_format_specifier() {
891 let mut b = CodeBlock::builder();
892 b.add("hello %X world", ());
893 let result = b.build();
894 assert!(result.is_err());
895 let err_msg = result.unwrap_err().to_string();
896 assert!(err_msg.contains("invalid format specifier"));
897 assert!(err_msg.contains("%X"));
898 }
899
900 #[test]
901 fn test_parse_format_invalid_specifier_returns_error() {
902 let result = parse_format("foo %Z bar");
903 assert!(result.is_err());
904 let err_msg = result.unwrap_err().to_string();
905 assert!(err_msg.contains("invalid format specifier"));
906 assert!(err_msg.contains("%Z"));
907 }
908
909 #[test]
910 fn test_mismatched_arg_count_includes_specifiers_and_kinds() {
911 let user = TypeName::importable("./models", "User");
912 let mut b = CodeBlock::builder();
913 b.add("%T %S %L", (user,));
914 let result = b.build();
915 assert!(result.is_err());
916 let err_msg = result.unwrap_err().to_string();
917 assert!(err_msg.contains("expects 3 args but got 1"));
918 assert!(err_msg.contains("%T"));
919 assert!(err_msg.contains("%S"));
920 assert!(err_msg.contains("%L"));
921 assert!(err_msg.contains("TypeName"));
922 }
923
924 #[test]
925 fn test_begin_control_flow_stores_condition() {
926 let mut b = CodeBlock::builder();
927 b.begin_control_flow("class Functor f", ());
928 b.add_statement("fmap :: (a -> b) -> f a -> f b", ());
929 b.end_control_flow();
930 let block = b.build().unwrap();
931 let has_open = block
932 .nodes
933 .iter()
934 .any(|n| matches!(n, CodeNode::BlockOpen(s) if s == "class Functor f"));
935 assert!(has_open, "should contain BlockOpen with condition text");
936 let has_close = block
937 .nodes
938 .iter()
939 .any(|n| matches!(n, CodeNode::BlockClose(s) if s == "class Functor f"));
940 assert!(has_close, "should contain BlockClose with condition text");
941 }
942
943 #[test]
944 fn test_begin_control_flow_match_empty_open() {
945 let mut b = CodeBlock::builder();
946 b.begin_control_flow("match x with", ());
947 b.add("| Red -> red", ());
948 b.add_line();
949 b.end_control_flow();
950 let block = b.build().unwrap();
951 let has_open = block
952 .nodes
953 .iter()
954 .any(|n| matches!(n, CodeNode::BlockOpen(s) if s == "match x with"));
955 assert!(has_open, "should contain BlockOpen(\"match x with\")");
956 }
957}