1use super::GeneratedCode;
7use crate::Language;
8
9#[derive(Debug, Clone, PartialEq)]
11#[allow(missing_docs)]
12pub enum RuchyNode {
13 Module(Vec<RuchyNode>),
15 FnDef {
17 name: String,
19 params: Vec<(String, RuchyType)>,
21 return_type: Option<RuchyType>,
23 body: Vec<RuchyNode>,
25 },
26 Let {
28 name: String,
30 ty: Option<RuchyType>,
32 value: Box<RuchyNode>,
34 mutable: bool,
36 },
37 Assign {
39 target: Box<RuchyNode>,
41 value: Box<RuchyNode>,
43 },
44 BinOp {
46 left: Box<RuchyNode>,
48 op: RuchyBinaryOp,
50 right: Box<RuchyNode>,
52 },
53 UnaryOp {
55 op: RuchyUnaryOp,
57 operand: Box<RuchyNode>,
59 },
60 IntLit(i64),
62 FloatLit(f64),
64 StrLit(String),
66 BoolLit(bool),
68 Ident(String),
70 If {
72 cond: Box<RuchyNode>,
74 then_body: Vec<RuchyNode>,
76 else_body: Vec<RuchyNode>,
78 },
79 Match {
81 value: Box<RuchyNode>,
83 arms: Vec<(RuchyNode, Vec<RuchyNode>)>,
85 },
86 While {
88 cond: Box<RuchyNode>,
90 body: Vec<RuchyNode>,
92 },
93 For {
95 var: String,
97 iter: Box<RuchyNode>,
99 body: Vec<RuchyNode>,
101 },
102 Return(Option<Box<RuchyNode>>),
104 Break,
106 Continue,
108 Call {
110 func: Box<RuchyNode>,
112 args: Vec<RuchyNode>,
114 },
115 MethodCall {
117 receiver: Box<RuchyNode>,
119 method: String,
121 args: Vec<RuchyNode>,
123 },
124 StructDef {
126 name: String,
128 fields: Vec<(String, RuchyType)>,
130 },
131 StructInit {
133 name: String,
135 fields: Vec<(String, RuchyNode)>,
137 },
138 FieldAccess {
140 receiver: Box<RuchyNode>,
142 field: String,
144 },
145 OptionalChain {
147 receiver: Box<RuchyNode>,
149 field: String,
151 },
152 Pipeline {
154 left: Box<RuchyNode>,
156 right: Box<RuchyNode>,
158 },
159 Array(Vec<RuchyNode>),
161 Range {
163 start: Box<RuchyNode>,
165 end: Box<RuchyNode>,
167 inclusive: bool,
169 },
170 Closure {
172 params: Vec<String>,
174 body: Box<RuchyNode>,
176 },
177 Block(Vec<RuchyNode>),
179 Spawn(Box<RuchyNode>),
181 Send {
183 target: Box<RuchyNode>,
185 message: Box<RuchyNode>,
187 },
188 Compare {
190 left: Box<RuchyNode>,
192 op: RuchyCompareOp,
194 right: Box<RuchyNode>,
196 },
197 NullCoalesce {
199 value: Box<RuchyNode>,
201 default: Box<RuchyNode>,
203 },
204 Pattern(RuchyPattern),
206}
207
208#[derive(Debug, Clone, PartialEq, Eq)]
210pub enum RuchyType {
211 I32,
213 I64,
215 F64,
217 Bool,
219 String,
221 Unit,
223 Option(Box<RuchyType>),
225 Vec(Box<RuchyType>),
227 Custom(String),
229}
230
231impl RuchyType {
232 #[must_use]
234 pub fn all_basic() -> &'static [Self] {
235 &[Self::I32, Self::I64, Self::F64, Self::Bool, Self::String]
236 }
237
238 #[must_use]
240 pub fn to_str(&self) -> String {
241 match self {
242 Self::I32 => "i32".to_string(),
243 Self::I64 => "i64".to_string(),
244 Self::F64 => "f64".to_string(),
245 Self::Bool => "bool".to_string(),
246 Self::String => "String".to_string(),
247 Self::Unit => "()".to_string(),
248 Self::Option(inner) => format!("Option<{}>", inner.to_str()),
249 Self::Vec(inner) => format!("Vec<{}>", inner.to_str()),
250 Self::Custom(name) => name.clone(),
251 }
252 }
253}
254
255#[derive(Debug, Clone, Copy, PartialEq, Eq)]
257pub enum RuchyBinaryOp {
258 Add,
260 Sub,
262 Mul,
264 Div,
266 Mod,
268 And,
270 Or,
272 BitAnd,
274 BitOr,
276 BitXor,
278}
279
280impl RuchyBinaryOp {
281 #[must_use]
283 pub fn arithmetic() -> &'static [Self] {
284 &[Self::Add, Self::Sub, Self::Mul, Self::Div, Self::Mod]
285 }
286
287 #[must_use]
289 pub fn to_str(self) -> &'static str {
290 match self {
291 Self::Add => "+",
292 Self::Sub => "-",
293 Self::Mul => "*",
294 Self::Div => "/",
295 Self::Mod => "%",
296 Self::And => "&&",
297 Self::Or => "||",
298 Self::BitAnd => "&",
299 Self::BitOr => "|",
300 Self::BitXor => "^",
301 }
302 }
303}
304
305#[derive(Debug, Clone, Copy, PartialEq, Eq)]
307pub enum RuchyUnaryOp {
308 Neg,
310 Not,
312 Ref,
314 RefMut,
316 Deref,
318}
319
320impl RuchyUnaryOp {
321 #[must_use]
323 pub fn all() -> &'static [Self] {
324 &[Self::Neg, Self::Not]
325 }
326
327 #[must_use]
329 pub fn to_str(self) -> &'static str {
330 match self {
331 Self::Neg => "-",
332 Self::Not => "!",
333 Self::Ref => "&",
334 Self::RefMut => "&mut ",
335 Self::Deref => "*",
336 }
337 }
338}
339
340#[derive(Debug, Clone, Copy, PartialEq, Eq)]
342pub enum RuchyCompareOp {
343 Eq,
345 Ne,
347 Lt,
349 Gt,
351 Le,
353 Ge,
355}
356
357impl RuchyCompareOp {
358 #[must_use]
360 pub fn all() -> &'static [Self] {
361 &[Self::Eq, Self::Ne, Self::Lt, Self::Gt, Self::Le, Self::Ge]
362 }
363
364 #[must_use]
366 pub fn to_str(self) -> &'static str {
367 match self {
368 Self::Eq => "==",
369 Self::Ne => "!=",
370 Self::Lt => "<",
371 Self::Gt => ">",
372 Self::Le => "<=",
373 Self::Ge => ">=",
374 }
375 }
376}
377
378#[derive(Debug, Clone, PartialEq)]
380pub enum RuchyPattern {
381 Wildcard,
383 Binding(String),
385 Literal(Box<RuchyNode>),
387 Tuple(Vec<RuchyPattern>),
389}
390
391impl RuchyNode {
392 #[must_use]
394 #[allow(clippy::too_many_lines)]
395 pub fn to_code(&self, indent: usize) -> String {
396 let indent_str = " ".repeat(indent);
397 match self {
398 Self::Module(items) => items
399 .iter()
400 .map(|item| item.to_code(0))
401 .collect::<Vec<_>>()
402 .join("\n\n"),
403 Self::FnDef {
404 name,
405 params,
406 return_type,
407 body,
408 } => {
409 let params_str = params
410 .iter()
411 .map(|(n, t)| format!("{}: {}", n, t.to_str()))
412 .collect::<Vec<_>>()
413 .join(", ");
414 let ret_str = return_type
415 .as_ref()
416 .map_or(String::new(), |t| format!(" -> {}", t.to_str()));
417 let body_str = body
418 .iter()
419 .map(|s| s.to_code(indent + 1))
420 .collect::<Vec<_>>()
421 .join("\n");
422 format!(
423 "{indent_str}fn {name}({params_str}){ret_str} {{\n{body_str}\n{indent_str}}}"
424 )
425 }
426 Self::Let {
427 name,
428 ty,
429 value,
430 mutable,
431 } => {
432 let mut_str = if *mutable { "mut " } else { "" };
433 let ty_str = ty
434 .as_ref()
435 .map_or(String::new(), |t| format!(": {}", t.to_str()));
436 format!(
437 "{}let {}{}{} = {};",
438 indent_str,
439 mut_str,
440 name,
441 ty_str,
442 value.to_code(0)
443 )
444 }
445 Self::Assign { target, value } => {
446 format!(
447 "{}{} = {};",
448 indent_str,
449 target.to_code(0),
450 value.to_code(0)
451 )
452 }
453 Self::BinOp { left, op, right } => {
454 format!("({} {} {})", left.to_code(0), op.to_str(), right.to_code(0))
455 }
456 Self::UnaryOp { op, operand } => {
457 format!("({}{})", op.to_str(), operand.to_code(0))
458 }
459 Self::IntLit(n) => n.to_string(),
460 Self::FloatLit(f) => format!("{f:.1}"),
461 Self::StrLit(s) => format!("\"{s}\""),
462 Self::BoolLit(b) => b.to_string(),
463 Self::Ident(name) => name.clone(),
464 Self::If {
465 cond,
466 then_body,
467 else_body,
468 } => {
469 let then_str = then_body
470 .iter()
471 .map(|s| s.to_code(indent + 1))
472 .collect::<Vec<_>>()
473 .join("\n");
474 if else_body.is_empty() {
475 format!(
476 "{}if {} {{\n{}\n{}}}",
477 indent_str,
478 cond.to_code(0),
479 then_str,
480 indent_str
481 )
482 } else {
483 let else_str = else_body
484 .iter()
485 .map(|s| s.to_code(indent + 1))
486 .collect::<Vec<_>>()
487 .join("\n");
488 format!(
489 "{}if {} {{\n{}\n{}}} else {{\n{}\n{}}}",
490 indent_str,
491 cond.to_code(0),
492 then_str,
493 indent_str,
494 else_str,
495 indent_str
496 )
497 }
498 }
499 Self::Match { value, arms } => {
500 let arms_str = arms
501 .iter()
502 .map(|(pat, body)| {
503 let body_str = body
504 .iter()
505 .map(|s| s.to_code(0))
506 .collect::<Vec<_>>()
507 .join("; ");
508 format!("{} {} => {{ {} }}", indent_str, pat.to_code(0), body_str)
509 })
510 .collect::<Vec<_>>()
511 .join(",\n");
512 format!(
513 "{}match {} {{\n{}\n{}}}",
514 indent_str,
515 value.to_code(0),
516 arms_str,
517 indent_str
518 )
519 }
520 Self::While { cond, body } => {
521 let body_str = body
522 .iter()
523 .map(|s| s.to_code(indent + 1))
524 .collect::<Vec<_>>()
525 .join("\n");
526 format!(
527 "{}while {} {{\n{}\n{}}}",
528 indent_str,
529 cond.to_code(0),
530 body_str,
531 indent_str
532 )
533 }
534 Self::For { var, iter, body } => {
535 let body_str = body
536 .iter()
537 .map(|s| s.to_code(indent + 1))
538 .collect::<Vec<_>>()
539 .join("\n");
540 format!(
541 "{}for {} in {} {{\n{}\n{}}}",
542 indent_str,
543 var,
544 iter.to_code(0),
545 body_str,
546 indent_str
547 )
548 }
549 Self::Return(expr) => {
550 if let Some(e) = expr {
551 format!("{}return {};", indent_str, e.to_code(0))
552 } else {
553 format!("{indent_str}return;")
554 }
555 }
556 Self::Break => format!("{indent_str}break;"),
557 Self::Continue => format!("{indent_str}continue;"),
558 Self::Call { func, args } => {
559 let args_str = args
560 .iter()
561 .map(|a| a.to_code(0))
562 .collect::<Vec<_>>()
563 .join(", ");
564 format!("{}({})", func.to_code(0), args_str)
565 }
566 Self::MethodCall {
567 receiver,
568 method,
569 args,
570 } => {
571 let args_str = args
572 .iter()
573 .map(|a| a.to_code(0))
574 .collect::<Vec<_>>()
575 .join(", ");
576 format!("{}.{}({})", receiver.to_code(0), method, args_str)
577 }
578 Self::StructDef { name, fields } => {
579 let fields_str = fields
580 .iter()
581 .map(|(n, t)| format!("{} {}: {},", indent_str, n, t.to_str()))
582 .collect::<Vec<_>>()
583 .join("\n");
584 format!("{indent_str}struct {name} {{\n{fields_str}\n{indent_str}}}")
585 }
586 Self::StructInit { name, fields } => {
587 let fields_str = fields
588 .iter()
589 .map(|(n, v)| format!("{}: {}", n, v.to_code(0)))
590 .collect::<Vec<_>>()
591 .join(", ");
592 format!("{name} {{ {fields_str} }}")
593 }
594 Self::FieldAccess { receiver, field } => {
595 format!("{}.{}", receiver.to_code(0), field)
596 }
597 Self::OptionalChain { receiver, field } => {
598 format!("{}?.{}", receiver.to_code(0), field)
599 }
600 Self::Pipeline { left, right } => {
601 format!("{} |> {}", left.to_code(0), right.to_code(0))
602 }
603 Self::Array(items) => {
604 let items_str = items
605 .iter()
606 .map(|i| i.to_code(0))
607 .collect::<Vec<_>>()
608 .join(", ");
609 format!("[{items_str}]")
610 }
611 Self::Range {
612 start,
613 end,
614 inclusive,
615 } => {
616 let op = if *inclusive { "..=" } else { ".." };
617 format!("{}{}{}", start.to_code(0), op, end.to_code(0))
618 }
619 Self::Closure { params, body } => {
620 let params_str = params.join(", ");
621 format!("|{}| {}", params_str, body.to_code(0))
622 }
623 Self::Block(stmts) => {
624 let stmts_str = stmts
625 .iter()
626 .map(|s| s.to_code(indent + 1))
627 .collect::<Vec<_>>()
628 .join("\n");
629 format!("{indent_str}{{\n{stmts_str}\n{indent_str}}}")
630 }
631 Self::Spawn(expr) => format!("spawn {}", expr.to_code(0)),
632 Self::Send { target, message } => {
633 format!("{} <- {}", target.to_code(0), message.to_code(0))
634 }
635 Self::Compare { left, op, right } => {
636 format!("({} {} {})", left.to_code(0), op.to_str(), right.to_code(0))
637 }
638 Self::NullCoalesce { value, default } => {
639 format!("{} ?? {}", value.to_code(0), default.to_code(0))
640 }
641 Self::Pattern(pat) => pat.to_code(),
642 }
643 }
644
645 #[must_use]
647 pub fn depth(&self) -> usize {
648 match self {
649 Self::Module(items) => 1 + items.iter().map(Self::depth).max().unwrap_or(0),
650 Self::FnDef { body, .. } => 1 + body.iter().map(Self::depth).max().unwrap_or(0),
651 Self::Let { value, .. } => 1 + value.depth(),
652 Self::Assign { target, value } => 1 + target.depth().max(value.depth()),
653 Self::BinOp { left, right, .. } | Self::Compare { left, right, .. } => {
654 1 + left.depth().max(right.depth())
655 }
656 Self::UnaryOp { operand, .. } => 1 + operand.depth(),
657 Self::If {
658 cond,
659 then_body,
660 else_body,
661 } => {
662 let then_depth = then_body.iter().map(Self::depth).max().unwrap_or(0);
663 let else_depth = else_body.iter().map(Self::depth).max().unwrap_or(0);
664 1 + cond.depth().max(then_depth).max(else_depth)
665 }
666 Self::Match { value, arms } => {
667 let arms_depth = arms
668 .iter()
669 .flat_map(|(_, body)| body.iter().map(Self::depth))
670 .max()
671 .unwrap_or(0);
672 1 + value.depth().max(arms_depth)
673 }
674 Self::While { cond, body } => {
675 let body_depth = body.iter().map(Self::depth).max().unwrap_or(0);
676 1 + cond.depth().max(body_depth)
677 }
678 Self::For { iter, body, .. } => {
679 let body_depth = body.iter().map(Self::depth).max().unwrap_or(0);
680 1 + iter.depth().max(body_depth)
681 }
682 Self::Return(Some(e)) => 1 + e.depth(),
683 Self::Call { func, args } => {
684 let args_depth = args.iter().map(Self::depth).max().unwrap_or(0);
685 1 + func.depth().max(args_depth)
686 }
687 Self::MethodCall { receiver, args, .. } => {
688 let args_depth = args.iter().map(Self::depth).max().unwrap_or(0);
689 1 + receiver.depth().max(args_depth)
690 }
691 Self::FieldAccess { receiver, .. } | Self::OptionalChain { receiver, .. } => {
692 1 + receiver.depth()
693 }
694 Self::Pipeline { left, right }
695 | Self::NullCoalesce {
696 value: left,
697 default: right,
698 } => 1 + left.depth().max(right.depth()),
699 Self::Array(items) => 1 + items.iter().map(Self::depth).max().unwrap_or(0),
700 Self::Range { start, end, .. } => 1 + start.depth().max(end.depth()),
701 Self::Closure { body, .. } => 1 + body.depth(),
702 Self::Block(stmts) => 1 + stmts.iter().map(Self::depth).max().unwrap_or(0),
703 Self::Spawn(e) => 1 + e.depth(),
704 Self::Send { target, message } => 1 + target.depth().max(message.depth()),
705 Self::StructInit { fields, .. } => {
706 1 + fields.iter().map(|(_, v)| v.depth()).max().unwrap_or(0)
707 }
708 Self::Return(None)
710 | Self::Break
711 | Self::Continue
712 | Self::IntLit(_)
713 | Self::FloatLit(_)
714 | Self::StrLit(_)
715 | Self::BoolLit(_)
716 | Self::Ident(_)
717 | Self::StructDef { .. }
718 | Self::Pattern(_) => 1,
719 }
720 }
721}
722
723impl RuchyPattern {
724 #[must_use]
726 pub fn to_code(&self) -> String {
727 match self {
728 Self::Wildcard => "_".to_string(),
729 Self::Binding(name) => name.clone(),
730 Self::Literal(lit) => lit.to_code(0),
731 Self::Tuple(pats) => {
732 let pats_str = pats
733 .iter()
734 .map(Self::to_code)
735 .collect::<Vec<_>>()
736 .join(", ");
737 format!("({pats_str})")
738 }
739 }
740 }
741}
742
743#[derive(Debug)]
745pub struct RuchyEnumerator {
746 max_depth: usize,
747 var_names: Vec<String>,
748 int_values: Vec<i64>,
749}
750
751impl Default for RuchyEnumerator {
752 fn default() -> Self {
753 Self::new(3)
754 }
755}
756
757impl RuchyEnumerator {
758 #[must_use]
760 pub fn new(max_depth: usize) -> Self {
761 Self {
762 max_depth,
763 var_names: vec!["x".to_string(), "y".to_string(), "n".to_string()],
764 int_values: vec![0, 1, -1, 2, 10, 42],
765 }
766 }
767
768 pub fn enumerate_expressions(&self, depth: usize) -> Vec<RuchyNode> {
770 if depth == 0 {
771 return vec![];
772 }
773
774 let mut results = Vec::new();
775
776 for val in &self.int_values {
778 results.push(RuchyNode::IntLit(*val));
779 }
780 results.push(RuchyNode::BoolLit(true));
781 results.push(RuchyNode::BoolLit(false));
782 for name in &self.var_names {
783 results.push(RuchyNode::Ident(name.clone()));
784 }
785
786 if depth >= 2 {
787 let sub_exprs = self.enumerate_expressions(depth - 1);
789 let limited: Vec<_> = sub_exprs.iter().take(15).collect();
790
791 for left in &limited {
792 for right in limited.iter().take(10) {
793 for op in RuchyBinaryOp::arithmetic() {
794 results.push(RuchyNode::BinOp {
795 left: Box::new((*left).clone()),
796 op: *op,
797 right: Box::new((*right).clone()),
798 });
799 }
800 }
801 }
802
803 for left in &limited {
805 for right in limited.iter().take(10) {
806 for op in RuchyCompareOp::all() {
807 results.push(RuchyNode::Compare {
808 left: Box::new((*left).clone()),
809 op: *op,
810 right: Box::new((*right).clone()),
811 });
812 }
813 }
814 }
815
816 for operand in limited.iter().take(8) {
818 for op in RuchyUnaryOp::all() {
819 results.push(RuchyNode::UnaryOp {
820 op: *op,
821 operand: Box::new((*operand).clone()),
822 });
823 }
824 }
825
826 for start in self.int_values.iter().take(3) {
828 for end in self.int_values.iter().take(3) {
829 results.push(RuchyNode::Range {
830 start: Box::new(RuchyNode::IntLit(*start)),
831 end: Box::new(RuchyNode::IntLit(*end)),
832 inclusive: false,
833 });
834 }
835 }
836
837 for left in limited.iter().take(5) {
839 results.push(RuchyNode::Pipeline {
840 left: Box::new((*left).clone()),
841 right: Box::new(RuchyNode::Ident("f".to_string())),
842 });
843 }
844 }
845
846 results
847 }
848
849 pub fn enumerate_statements(&self, depth: usize) -> Vec<RuchyNode> {
851 if depth == 0 {
852 return vec![];
853 }
854
855 let mut results = Vec::new();
856
857 let exprs = self.enumerate_expressions(depth - 1);
858 let limited_exprs: Vec<_> = exprs.iter().take(20).collect();
859
860 for name in &self.var_names {
862 for value in &limited_exprs {
863 results.push(RuchyNode::Let {
864 name: name.clone(),
865 ty: None,
866 value: Box::new((*value).clone()),
867 mutable: false,
868 });
869 results.push(RuchyNode::Let {
870 name: name.clone(),
871 ty: None,
872 value: Box::new((*value).clone()),
873 mutable: true,
874 });
875 }
876 }
877
878 results.push(RuchyNode::Return(None));
880 for expr in limited_exprs.iter().take(10) {
881 results.push(RuchyNode::Return(Some(Box::new((*expr).clone()))));
882 }
883
884 results.push(RuchyNode::Break);
886 results.push(RuchyNode::Continue);
887
888 if depth >= 2 {
889 let conditions: Vec<_> = exprs
891 .iter()
892 .filter(|e| matches!(e, RuchyNode::Compare { .. } | RuchyNode::BoolLit(_)))
893 .take(5)
894 .collect();
895
896 let body_stmts = self.enumerate_statements(depth - 1);
897 let limited_body: Vec<_> = body_stmts.iter().take(5).collect();
898
899 for cond in &conditions {
900 for body in &limited_body {
901 results.push(RuchyNode::If {
902 cond: Box::new((*cond).clone()),
903 then_body: vec![(*body).clone()],
904 else_body: vec![],
905 });
906 }
907 }
908
909 for cond in &conditions {
911 results.push(RuchyNode::While {
912 cond: Box::new((*cond).clone()),
913 body: vec![RuchyNode::Break],
914 });
915 }
916
917 for name in self.var_names.iter().take(2) {
919 results.push(RuchyNode::For {
920 var: name.clone(),
921 iter: Box::new(RuchyNode::Range {
922 start: Box::new(RuchyNode::IntLit(0)),
923 end: Box::new(RuchyNode::IntLit(10)),
924 inclusive: false,
925 }),
926 body: vec![RuchyNode::Break],
927 });
928 }
929 }
930
931 results
932 }
933
934 #[must_use]
936 pub fn enumerate_programs(&self) -> Vec<GeneratedCode> {
937 let mut results = Vec::new();
938
939 let stmts = self.enumerate_statements(self.max_depth);
940
941 for stmt in stmts.iter().take(50) {
943 let func = RuchyNode::FnDef {
944 name: "main".to_string(),
945 params: vec![],
946 return_type: None,
947 body: vec![stmt.clone()],
948 };
949 let module = RuchyNode::Module(vec![func]);
950 let code = module.to_code(0);
951 results.push(GeneratedCode {
952 code,
953 language: Language::Ruchy,
954 ast_depth: stmt.depth() + 2,
955 features: self.extract_features(stmt),
956 });
957 }
958
959 for stmt in stmts.iter().take(20) {
961 let func = RuchyNode::FnDef {
962 name: "compute".to_string(),
963 params: vec![
964 ("a".to_string(), RuchyType::I32),
965 ("b".to_string(), RuchyType::I32),
966 ],
967 return_type: Some(RuchyType::I32),
968 body: vec![stmt.clone()],
969 };
970 let module = RuchyNode::Module(vec![func]);
971 let code = module.to_code(0);
972 results.push(GeneratedCode {
973 code,
974 language: Language::Ruchy,
975 ast_depth: stmt.depth() + 2,
976 features: self.extract_features(stmt),
977 });
978 }
979
980 results
981 }
982
983 fn extract_features(&self, node: &RuchyNode) -> Vec<String> {
985 let mut features = Vec::new();
986
987 match node {
988 RuchyNode::Let { mutable, .. } => {
989 features.push("let".to_string());
990 if *mutable {
991 features.push("mut".to_string());
992 }
993 }
994 RuchyNode::BinOp { op, .. } => {
995 features.push("binop".to_string());
996 features.push(format!("op_{}", op.to_str()));
997 }
998 RuchyNode::If { else_body, .. } => {
999 features.push("if".to_string());
1000 if !else_body.is_empty() {
1001 features.push("else".to_string());
1002 }
1003 }
1004 RuchyNode::While { .. } => features.push("while".to_string()),
1005 RuchyNode::For { .. } => features.push("for".to_string()),
1006 RuchyNode::Return(_) => features.push("return".to_string()),
1007 RuchyNode::Compare { op, .. } => {
1008 features.push("compare".to_string());
1009 features.push(format!("cmp_{}", op.to_str()));
1010 }
1011 RuchyNode::Pipeline { .. } => features.push("pipeline".to_string()),
1012 RuchyNode::Range { inclusive, .. } => {
1013 features.push("range".to_string());
1014 if *inclusive {
1015 features.push("inclusive".to_string());
1016 }
1017 }
1018 RuchyNode::Match { .. } => features.push("match".to_string()),
1019 RuchyNode::Spawn(_) => features.push("spawn".to_string()),
1020 RuchyNode::Send { .. } => features.push("send".to_string()),
1021 _ => {}
1022 }
1023
1024 features
1025 }
1026}
1027
1028#[cfg(test)]
1029mod tests {
1030 use super::*;
1031
1032 #[test]
1033 fn test_int_lit_to_code() {
1034 let node = RuchyNode::IntLit(42);
1035 assert_eq!(node.to_code(0), "42");
1036 }
1037
1038 #[test]
1039 fn test_let_to_code() {
1040 let node = RuchyNode::Let {
1041 name: "x".to_string(),
1042 ty: None,
1043 value: Box::new(RuchyNode::IntLit(0)),
1044 mutable: false,
1045 };
1046 assert_eq!(node.to_code(0), "let x = 0;");
1047 }
1048
1049 #[test]
1050 fn test_let_mut_to_code() {
1051 let node = RuchyNode::Let {
1052 name: "x".to_string(),
1053 ty: Some(RuchyType::I32),
1054 value: Box::new(RuchyNode::IntLit(0)),
1055 mutable: true,
1056 };
1057 assert_eq!(node.to_code(0), "let mut x: i32 = 0;");
1058 }
1059
1060 #[test]
1061 fn test_fn_def_to_code() {
1062 let node = RuchyNode::FnDef {
1063 name: "main".to_string(),
1064 params: vec![],
1065 return_type: None,
1066 body: vec![RuchyNode::Return(None)],
1067 };
1068 let code = node.to_code(0);
1069 assert!(code.contains("fn main()"));
1070 assert!(code.contains("return;"));
1071 }
1072
1073 #[test]
1074 fn test_pipeline_to_code() {
1075 let node = RuchyNode::Pipeline {
1076 left: Box::new(RuchyNode::IntLit(1)),
1077 right: Box::new(RuchyNode::Ident("f".to_string())),
1078 };
1079 assert_eq!(node.to_code(0), "1 |> f");
1080 }
1081
1082 #[test]
1083 fn test_range_to_code() {
1084 let node = RuchyNode::Range {
1085 start: Box::new(RuchyNode::IntLit(0)),
1086 end: Box::new(RuchyNode::IntLit(10)),
1087 inclusive: false,
1088 };
1089 assert_eq!(node.to_code(0), "0..10");
1090
1091 let inclusive = RuchyNode::Range {
1092 start: Box::new(RuchyNode::IntLit(0)),
1093 end: Box::new(RuchyNode::IntLit(10)),
1094 inclusive: true,
1095 };
1096 assert_eq!(inclusive.to_code(0), "0..=10");
1097 }
1098
1099 #[test]
1100 fn test_for_to_code() {
1101 let node = RuchyNode::For {
1102 var: "i".to_string(),
1103 iter: Box::new(RuchyNode::Range {
1104 start: Box::new(RuchyNode::IntLit(0)),
1105 end: Box::new(RuchyNode::IntLit(10)),
1106 inclusive: false,
1107 }),
1108 body: vec![RuchyNode::Break],
1109 };
1110 let code = node.to_code(0);
1111 assert!(code.contains("for i in 0..10"));
1112 assert!(code.contains("break;"));
1113 }
1114
1115 #[test]
1116 fn test_enumerator_creates_programs() {
1117 let enumerator = RuchyEnumerator::new(2);
1118 let programs = enumerator.enumerate_programs();
1119 assert!(!programs.is_empty(), "Should generate programs");
1120 }
1121
1122 #[test]
1123 fn test_programs_are_ruchy() {
1124 let enumerator = RuchyEnumerator::new(2);
1125 let programs = enumerator.enumerate_programs();
1126 for prog in &programs {
1127 assert_eq!(prog.language, Language::Ruchy);
1128 }
1129 }
1130
1131 #[test]
1132 fn test_depth_calculation() {
1133 let node = RuchyNode::BinOp {
1134 left: Box::new(RuchyNode::IntLit(1)),
1135 op: RuchyBinaryOp::Add,
1136 right: Box::new(RuchyNode::BinOp {
1137 left: Box::new(RuchyNode::IntLit(2)),
1138 op: RuchyBinaryOp::Mul,
1139 right: Box::new(RuchyNode::IntLit(3)),
1140 }),
1141 };
1142 assert_eq!(node.depth(), 3);
1143 }
1144
1145 #[test]
1146 fn test_type_to_str() {
1147 assert_eq!(RuchyType::I32.to_str(), "i32");
1148 assert_eq!(
1149 RuchyType::Option(Box::new(RuchyType::I32)).to_str(),
1150 "Option<i32>"
1151 );
1152 }
1153
1154 #[test]
1155 fn test_extract_features() {
1156 let enumerator = RuchyEnumerator::new(2);
1157 let node = RuchyNode::Let {
1158 name: "x".to_string(),
1159 ty: None,
1160 value: Box::new(RuchyNode::IntLit(0)),
1161 mutable: true,
1162 };
1163 let features = enumerator.extract_features(&node);
1164 assert!(features.contains(&"let".to_string()));
1165 assert!(features.contains(&"mut".to_string()));
1166 }
1167}