1use std::fmt::Write;
15use xpile_backend::{Artifact, Backend, BackendConfig, BackendError, QuorumStatus, Target};
16use xpile_meta_hir::{
17 BinOp, Block, Expr, FloatOp, Function, Item, Module, NumBuiltinOp, Param, SourceLang, Stmt,
18 StrMethodOp, Type, UnOp,
19};
20
21fn float_op_sym(op: FloatOp) -> &'static str {
23 match op {
24 FloatOp::Add => "+",
25 FloatOp::Sub => "-",
26 FloatOp::Mul => "*",
27 FloatOp::Div => "/",
28 }
29}
30
31#[derive(Debug, thiserror::Error)]
32pub enum CodegenError {
33 #[error("unsupported item: {0}")]
34 Unsupported(String),
35 #[error("formatting error: {0}")]
36 Format(#[from] std::fmt::Error),
37}
38
39pub fn emit_module(module: &Module) -> Result<String, CodegenError> {
40 let mut out = String::new();
41 writeln!(
42 out,
43 "// xpile-generated from {:?} module {}",
44 module.source_lang, module.name
45 )?;
46 writeln!(out)?;
47 let is_c = matches!(module.source_lang, SourceLang::C);
52 for item in &module.items {
53 match item {
54 Item::Function(f) => {
55 if is_c {
56 emit_c_function(&mut out, f)?;
57 } else {
58 emit_function(&mut out, f)?;
59 }
60 }
61 }
62 }
63 Ok(out)
64}
65
66fn emit_function(out: &mut String, f: &Function) -> Result<(), CodegenError> {
67 emit_contract_citations(out, f)?;
68 write!(out, "pub fn {}(", f.name)?;
69 for (i, p) in f.params.iter().enumerate() {
70 if i > 0 {
71 write!(out, ", ")?;
72 }
73 emit_param(out, p)?;
74 }
75 write!(out, ") -> ")?;
76 emit_type(out, &f.return_type)?;
77 writeln!(out, " {{")?;
78 let mode = function_bigint_mode(f);
79 emit_block(out, &f.body, mode)?;
80 writeln!(out, "}}")?;
81 Ok(())
82}
83
84fn function_bigint_mode(f: &Function) -> bool {
90 if matches!(f.return_type, Type::BigInt) {
91 return true;
92 }
93 if f.params.iter().any(|p| matches!(p.ty, Type::BigInt)) {
94 return true;
95 }
96 fn stmt_has_bigint(s: &Stmt) -> bool {
97 match s {
98 Stmt::Let { ty, .. } => matches!(ty, Type::BigInt),
99 Stmt::LetTuple { .. } => false,
102 Stmt::Assign { .. } | Stmt::Assert { .. } | Stmt::Return(_) | Stmt::Raise { .. } => {
106 false
107 }
108 Stmt::While { body, .. }
109 | Stmt::ForEach { body, .. }
110 | Stmt::ForEachPair { body, .. } => body.iter().any(stmt_has_bigint),
111 Stmt::If {
113 then_body,
114 else_body,
115 ..
116 } => then_body.iter().any(stmt_has_bigint) || else_body.iter().any(stmt_has_bigint),
117 Stmt::ListAppend { .. } | Stmt::SetAdd { .. } => false,
120 Stmt::IndexAssign { .. } => false,
122 Stmt::DictSet { .. } => false,
125 Stmt::Cmd { .. } => false,
131 Stmt::Pipeline { .. } => false,
134 Stmt::ShellLoop { .. } => false,
137 Stmt::ShellAssign { .. } => false,
139 }
140 }
141 f.body.stmts.iter().any(stmt_has_bigint)
142}
143
144fn emit_contract_citations(out: &mut String, f: &Function) -> Result<(), CodegenError> {
152 for id in f.applicable_contracts() {
153 writeln!(out, "// xpile-contract: {id}")?;
154 }
155 Ok(())
156}
157
158fn emit_block(out: &mut String, block: &Block, mode: bool) -> Result<(), CodegenError> {
159 for stmt in &block.stmts {
160 emit_stmt(out, stmt, mode)?;
161 }
162 write!(out, " ")?;
163 emit_expr(out, &block.trailing_return, mode)?;
164 writeln!(out)?;
165 Ok(())
166}
167
168fn emit_stmt(out: &mut String, stmt: &Stmt, mode: bool) -> Result<(), CodegenError> {
169 emit_stmt_indented(out, stmt, " ", mode)
170}
171
172fn emit_stmt_indented(
173 out: &mut String,
174 stmt: &Stmt,
175 indent: &str,
176 mode: bool,
177) -> Result<(), CodegenError> {
178 match stmt {
179 Stmt::Let {
180 name,
181 ty,
182 value,
183 mutable,
184 } => {
185 let kw = if *mutable { "let mut" } else { "let" };
186 write!(out, "{indent}{kw} {name}: ")?;
187 emit_type(out, ty)?;
188 write!(out, " = ")?;
189 emit_expr(out, value, mode)?;
190 writeln!(out, ";")?;
191 Ok(())
192 }
193 Stmt::Assign { name, value } => {
194 write!(out, "{indent}{name} = ")?;
195 emit_expr(out, value, mode)?;
196 writeln!(out, ";")?;
197 Ok(())
198 }
199 Stmt::LetTuple { names, value } => {
201 write!(out, "{indent}let ({}) = ", names.join(", "))?;
202 emit_expr(out, value, mode)?;
203 writeln!(out, ";")?;
204 Ok(())
205 }
206 Stmt::Return(e) => {
208 write!(out, "{indent}return ")?;
209 emit_expr(out, e, mode)?;
210 writeln!(out, ";")?;
211 Ok(())
212 }
213 Stmt::If {
216 cond,
217 then_body,
218 else_body,
219 } => {
220 write!(out, "{indent}if ")?;
221 emit_expr(out, cond, mode)?;
222 writeln!(out, " {{")?;
223 let inner = format!("{indent} ");
224 for s in then_body {
225 emit_stmt_indented(out, s, &inner, mode)?;
226 }
227 if else_body.is_empty() {
228 writeln!(out, "{indent}}}")?;
229 } else {
230 writeln!(out, "{indent}}} else {{")?;
231 for s in else_body {
232 emit_stmt_indented(out, s, &inner, mode)?;
233 }
234 writeln!(out, "{indent}}}")?;
235 }
236 Ok(())
237 }
238 Stmt::While { cond, body } => {
239 write!(out, "{indent}while ")?;
240 emit_expr(out, cond, mode)?;
241 writeln!(out, " {{")?;
242 let inner = format!("{indent} ");
243 for s in body {
244 emit_stmt_indented(out, s, &inner, mode)?;
245 }
246 writeln!(out, "{indent}}}")?;
247 Ok(())
248 }
249 Stmt::ForEach {
255 var,
256 iter,
257 body,
258 over_keys,
259 ..
260 } => {
261 let method = if *over_keys { "keys" } else { "iter" };
265 write!(out, "{indent}for {var} in ")?;
266 emit_expr(out, iter, mode)?;
267 writeln!(out, ".{method}().cloned() {{")?;
268 let inner = format!("{indent} ");
269 for s in body {
270 emit_stmt_indented(out, s, &inner, mode)?;
271 }
272 writeln!(out, "{indent}}}")?;
273 Ok(())
274 }
275 Stmt::ForEachPair {
278 first,
279 second,
280 iter,
281 kind,
282 body,
283 } => {
284 write!(out, "{indent}for ({first}, {second}) in ")?;
285 emit_expr(out, iter, mode)?;
286 match kind {
287 xpile_meta_hir::PairIterKind::Enumerate => {
288 out.push_str(
289 ".iter().cloned().enumerate().map(|(__i, __e)| (__i as i64, __e))",
290 );
291 }
292 xpile_meta_hir::PairIterKind::Zip(other) => {
293 out.push_str(".iter().cloned().zip(");
294 emit_expr(out, other, mode)?;
295 out.push_str(".iter().cloned())");
296 }
297 }
298 writeln!(out, " {{")?;
299 let inner = format!("{indent} ");
300 for s in body {
301 emit_stmt_indented(out, s, &inner, mode)?;
302 }
303 writeln!(out, "{indent}}}")?;
304 Ok(())
305 }
306 Stmt::ListAppend { list_name, elem } => {
310 write!(out, "{indent}{list_name}.push(")?;
311 emit_expr(out, elem, mode)?;
312 writeln!(out, ");")?;
313 Ok(())
314 }
315 Stmt::SetAdd { set_name, elem } => {
317 write!(out, "{indent}{set_name}.insert(")?;
318 emit_expr(out, elem, mode)?;
319 writeln!(out, ");")?;
320 Ok(())
321 }
322 Stmt::IndexAssign {
326 list_name,
327 index,
328 value,
329 } => {
330 write!(out, "{indent}{list_name}[")?;
331 emit_expr(out, index, mode)?;
332 out.push_str(" as usize] = ");
333 emit_expr(out, value, mode)?;
334 writeln!(out, ";")?;
335 Ok(())
336 }
337 Stmt::DictSet {
356 dict_name,
357 key,
358 value,
359 } => {
360 write!(out, "{indent}{{ let __xpile_dict_val = ")?;
361 emit_expr(out, value, mode)?;
362 write!(out, "; {dict_name}.insert(")?;
363 emit_expr(out, key, mode)?;
364 writeln!(out, ".clone(), __xpile_dict_val); }}")?;
365 Ok(())
366 }
367 Stmt::Assert { cond } => {
368 write!(out, "{indent}assert!(")?;
369 emit_expr(out, cond, mode)?;
370 writeln!(out, ");")?;
371 Ok(())
372 }
373 Stmt::Raise { message } => {
377 write!(out, "{indent}panic!(\"{{}}\", ")?;
378 emit_expr(out, message, mode)?;
379 writeln!(out, ");")?;
380 Ok(())
381 }
382 Stmt::Cmd { program, args } => Err(CodegenError::Unsupported(format!(
393 "Rust backend does not lower Stmt::Cmd (`{program}` with {} arg(s)) — \
394 contract C-BASHRS-POSIX-IDEMPOTENCE governs this construct; \
395 use `--target shell` to emit POSIX sh via bashrs-backend",
396 args.len()
397 ))),
398 Stmt::Pipeline { stages } => Err(CodegenError::Unsupported(format!(
401 "Rust backend does not lower Stmt::Pipeline ({} stages) — \
402 contract C-BASHRS-POSIX-IDEMPOTENCE governs shell pipelines; \
403 use `--target shell` to emit POSIX sh via bashrs-backend",
404 stages.len()
405 ))),
406 Stmt::ShellLoop { .. } => Err(CodegenError::Unsupported(
408 "Rust backend does not lower Stmt::ShellLoop — \
409 contract C-BASHRS-POSIX-IDEMPOTENCE governs shell loops; \
410 use `--target shell`"
411 .into(),
412 )),
413 Stmt::ShellAssign { name, .. } => Err(CodegenError::Unsupported(format!(
415 "Rust backend does not lower Stmt::ShellAssign (`{name}=…`) — \
416 contract C-BASHRS-POSIX-IDEMPOTENCE governs shell variable assignment; \
417 use `--target shell`"
418 ))),
419 }
420}
421
422fn emit_param(out: &mut String, p: &Param) -> Result<(), CodegenError> {
423 if p.mutable {
427 write!(out, "mut ")?;
428 }
429 write!(out, "{}: ", p.name)?;
430 emit_type(out, &p.ty)?;
431 Ok(())
432}
433
434fn escape_rust_str(s: &str) -> String {
439 let mut out = String::with_capacity(s.len());
440 for c in s.chars() {
441 match c {
442 '\\' => out.push_str("\\\\"),
443 '"' => out.push_str("\\\""),
444 other => out.push(other),
445 }
446 }
447 out
448}
449
450fn emit_type(out: &mut String, t: &Type) -> Result<(), CodegenError> {
451 match t {
452 Type::I64 => out.push_str("i64"),
453 Type::F64 => out.push_str("f64"),
455 Type::Bool => out.push_str("bool"),
456 Type::BigInt => out.push_str("xpile_bigint::BigInt"),
462 Type::Str => out.push_str("String"),
466 Type::List(elem_ty) => {
470 out.push_str("Vec<");
471 emit_type(out, elem_ty)?;
472 out.push('>');
473 }
474 Type::Dict(k_ty, v_ty) => {
479 out.push_str("std::collections::HashMap<");
480 emit_type(out, k_ty)?;
481 out.push_str(", ");
482 emit_type(out, v_ty)?;
483 out.push('>');
484 }
485 Type::Set(elem_ty) => {
487 out.push_str("std::collections::HashSet<");
488 emit_type(out, elem_ty)?;
489 out.push('>');
490 }
491 Type::Tuple(elems) => {
493 out.push('(');
494 for (i, t) in elems.iter().enumerate() {
495 if i > 0 {
496 out.push_str(", ");
497 }
498 emit_type(out, t)?;
499 }
500 out.push(')');
501 }
502 Type::ShellString | Type::ExitCode => {
510 return Err(CodegenError::Unsupported(format!(
511 "Rust backend does not lower {t:?} — \
512 contract C-BASHRS-POSIX-IDEMPOTENCE governs the bashrs type domain; \
513 use `--target shell` for shell-typed signatures"
514 )));
515 }
516 }
517 Ok(())
518}
519
520fn emit_expr(out: &mut String, e: &Expr, mode: bool) -> Result<(), CodegenError> {
521 match e {
522 Expr::Ident(name) => {
523 if mode {
530 write!(out, "{}.clone()", name)?;
531 } else {
532 write!(out, "{}", name)?;
533 }
534 }
535 Expr::LitInt(v) => {
536 if mode {
537 write!(out, "xpile_bigint::BigInt::from({}i64)", v)?;
540 } else {
541 write!(out, "{}i64", v)?;
542 }
543 }
544 Expr::LitFloat(v) => write!(out, "{}f64", v)?,
547 Expr::FloatBinOp { op, lhs, rhs } => {
548 out.push('(');
549 emit_expr(out, lhs, mode)?;
550 write!(out, " {} ", float_op_sym(*op))?;
551 emit_expr(out, rhs, mode)?;
552 out.push(')');
553 }
554 Expr::LitBool(b) => write!(out, "{}", b)?,
557 Expr::BinOp { op, lhs, rhs } => emit_binop(out, *op, lhs, rhs, mode)?,
558 Expr::Concat { lhs, rhs } => {
567 out.push_str("format!(\"{}{}\", ");
568 emit_expr(out, lhs, mode)?;
569 out.push_str(", ");
570 emit_expr(out, rhs, mode)?;
571 out.push(')');
572 }
573 Expr::StrMethod { recv, op, args } => {
578 if matches!(op, StrMethodOp::Join) {
581 emit_expr(out, &args[0], mode)?;
582 out.push_str(".join(&(");
583 emit_expr(out, recv, mode)?;
584 out.push_str(")[..])");
585 } else {
586 emit_expr(out, recv, mode)?;
587 match op {
588 StrMethodOp::Upper => out.push_str(".to_uppercase()"),
589 StrMethodOp::Lower => out.push_str(".to_lowercase()"),
590 StrMethodOp::Strip => out.push_str(".trim().to_string()"),
591 StrMethodOp::StartsWith | StrMethodOp::EndsWith => {
592 out.push_str(if matches!(op, StrMethodOp::StartsWith) {
593 ".starts_with(&("
594 } else {
595 ".ends_with(&("
596 });
597 emit_expr(out, &args[0], mode)?;
598 out.push_str(")[..])");
599 }
600 StrMethodOp::Split => {
602 out.push_str(".split(&(");
603 emit_expr(out, &args[0], mode)?;
604 out.push_str(")[..]).map(|__c| __c.to_string()).collect::<Vec<String>>()");
605 }
606 StrMethodOp::Replace => {
608 out.push_str(".replace(&(");
609 emit_expr(out, &args[0], mode)?;
610 out.push_str(")[..], &(");
611 emit_expr(out, &args[1], mode)?;
612 out.push_str(")[..])");
613 }
614 StrMethodOp::Join => unreachable!("Join handled above"),
615 }
616 }
617 }
618 Expr::ListLit(elems) => {
622 out.push_str("vec![");
623 for (i, e) in elems.iter().enumerate() {
624 if i > 0 {
625 out.push_str(", ");
626 }
627 emit_expr(out, e, mode)?;
628 }
629 out.push(']');
630 }
631 Expr::TupleLit(elems) => {
633 out.push('(');
634 for (i, e) in elems.iter().enumerate() {
635 if i > 0 {
636 out.push_str(", ");
637 }
638 emit_expr(out, e, mode)?;
639 }
640 out.push(')');
641 }
642 Expr::Slice {
645 collection,
646 lo,
647 hi,
648 of_str,
649 } => {
650 emit_expr(out, collection, mode)?;
651 out.push_str("[(");
652 emit_expr(out, lo, mode)?;
653 out.push_str(") as usize..(");
654 emit_expr(out, hi, mode)?;
655 out.push_str(") as usize]");
656 out.push_str(if *of_str { ".to_string()" } else { ".to_vec()" });
657 }
658 Expr::NumBuiltin { op, args } => {
660 out.push('(');
661 emit_expr(out, &args[0], mode)?;
662 out.push(')');
663 match op {
664 NumBuiltinOp::Abs => out.push_str(".abs()"),
665 NumBuiltinOp::Min | NumBuiltinOp::Max => {
666 out.push_str(if matches!(op, NumBuiltinOp::Min) {
667 ".min("
668 } else {
669 ".max("
670 });
671 emit_expr(out, &args[1], mode)?;
672 out.push(')');
673 }
674 }
675 }
676 Expr::Sum { list, of_float } => {
678 emit_expr(out, list, mode)?;
679 out.push_str(if *of_float {
680 ".iter().sum::<f64>()"
681 } else {
682 ".iter().sum::<i64>()"
683 });
684 }
685 Expr::ListMinMax { list, is_max } => {
687 emit_expr(out, list, mode)?;
688 out.push_str(if *is_max {
689 ".iter().copied().max().unwrap()"
690 } else {
691 ".iter().copied().min().unwrap()"
692 });
693 }
694 Expr::Sorted { list } => {
696 out.push_str("{ let mut __xv = ");
697 emit_expr(out, list, mode)?;
698 out.push_str(".clone(); __xv.sort(); __xv }");
699 }
700 Expr::Reversed { list } => {
702 out.push_str("{ let mut __xv = ");
703 emit_expr(out, list, mode)?;
704 out.push_str(".clone(); __xv.reverse(); __xv }");
705 }
706 Expr::DictLit(pairs) => {
710 if pairs.is_empty() {
715 out.push_str("std::collections::HashMap::new()");
716 } else {
717 out.push_str("{ let mut m = std::collections::HashMap::new(); ");
718 for (k, v) in pairs {
719 out.push_str("m.insert(");
720 emit_expr(out, k, mode)?;
721 out.push_str(", ");
722 emit_expr(out, v, mode)?;
723 out.push_str("); ");
724 }
725 out.push_str("m }");
726 }
727 }
728 Expr::Index { collection, index } => {
737 emit_expr(out, collection, mode)?;
738 out.push('[');
739 emit_expr(out, index, mode)?;
740 out.push_str(" as usize].clone()");
741 }
742 Expr::DictGet { dict, key } => {
748 emit_expr(out, dict, mode)?;
749 out.push_str("[&(");
750 emit_expr(out, key, mode)?;
751 out.push_str(")].clone()");
752 }
753 Expr::DictGetOr { dict, key, default } => {
757 emit_expr(out, dict, mode)?;
758 out.push_str(".get(&(");
759 emit_expr(out, key, mode)?;
760 out.push_str(")).cloned().unwrap_or(");
761 emit_expr(out, default, mode)?;
762 out.push(')');
763 }
764 Expr::DictContains { dict, key } => {
766 emit_expr(out, dict, mode)?;
767 out.push_str(".contains_key(&(");
768 emit_expr(out, key, mode)?;
769 out.push_str("))");
770 }
771 Expr::SetLit(elems) => {
776 if elems.is_empty() {
777 out.push_str("std::collections::HashSet::new()");
778 } else {
779 out.push_str("{ let mut __xset = std::collections::HashSet::new(); ");
780 for e in elems {
781 out.push_str("__xset.insert(");
782 emit_expr(out, e, mode)?;
783 out.push_str("); ");
784 }
785 out.push_str("__xset }");
786 }
787 }
788 Expr::SetContains { set, elem } => {
790 emit_expr(out, set, mode)?;
791 out.push_str(".contains(&(");
792 emit_expr(out, elem, mode)?;
793 out.push_str("))");
794 }
795 Expr::Len(inner) => {
800 emit_expr(out, inner, mode)?;
801 out.push_str(".len() as i64");
802 }
803 Expr::IfExpr {
804 cond,
805 then_expr,
806 else_expr,
807 } => emit_if_expr(out, cond, then_expr, else_expr, mode)?,
808 Expr::Call { callee, args } => emit_call(out, callee, args, mode)?,
809 Expr::UnOp { op, operand } => emit_unop(out, *op, operand, mode)?,
810 Expr::LitStr(s) => {
815 write!(out, "String::from(\"{}\")", escape_rust_str(s))?;
816 }
817 Expr::QuotedString { .. } => {
821 return Err(CodegenError::Unsupported(
822 "Rust backend does not lower Expr::QuotedString — \
823 contract C-BASHRS-POSIX-IDEMPOTENCE governs quoted shell strings; \
824 use `--target shell`"
825 .into(),
826 ));
827 }
828 Expr::ShellVar(name) => {
830 return Err(CodegenError::Unsupported(format!(
831 "Rust backend does not lower Expr::ShellVar (${name}) — \
832 contract C-BASHRS-POSIX-IDEMPOTENCE governs shell variable references; \
833 use `--target shell`"
834 )));
835 }
836 Expr::CommandSubstitution(_) => {
838 return Err(CodegenError::Unsupported(
839 "Rust backend does not lower Expr::CommandSubstitution — \
840 contract C-BASHRS-POSIX-IDEMPOTENCE governs shell substitution; \
841 use `--target shell`"
842 .into(),
843 ));
844 }
845 Expr::ShellSpecial(name) => {
847 return Err(CodegenError::Unsupported(format!(
848 "Rust backend does not lower Expr::ShellSpecial (${name}) — \
849 contract C-BASHRS-POSIX-IDEMPOTENCE governs shell special params; \
850 use `--target shell`"
851 )));
852 }
853 }
854 Ok(())
855}
856
857fn emit_unop(out: &mut String, op: UnOp, operand: &Expr, mode: bool) -> Result<(), CodegenError> {
858 match op {
859 UnOp::Neg => {
860 if mode {
861 write!(out, "(-")?;
864 emit_expr(out, operand, mode)?;
865 write!(out, ")")?;
866 } else {
867 write!(out, "(")?;
872 emit_expr(out, operand, mode)?;
873 write!(
874 out,
875 ").checked_neg().expect(\"xpile: i64 negation overflow; bigint promotion (contract C-PY-INT-ARITH slow path) not yet implemented\")"
876 )?;
877 }
878 }
879 UnOp::Not => {
880 write!(out, "(!")?;
881 emit_expr(out, operand, mode)?;
882 write!(out, ")")?;
883 }
884 }
885 Ok(())
886}
887
888fn emit_call(
889 out: &mut String,
890 callee: &str,
891 args: &[Expr],
892 mode: bool,
893) -> Result<(), CodegenError> {
894 write!(out, "{}(", callee)?;
895 for (i, a) in args.iter().enumerate() {
896 if i > 0 {
897 write!(out, ", ")?;
898 }
899 emit_expr(out, a, mode)?;
900 }
901 write!(out, ")")?;
902 Ok(())
903}
904
905fn emit_if_expr(
909 out: &mut String,
910 cond: &Expr,
911 then_expr: &Expr,
912 else_expr: &Expr,
913 mode: bool,
914) -> Result<(), CodegenError> {
915 write!(out, "if ")?;
916 emit_expr(out, cond, mode)?;
917 write!(out, " {{ ")?;
918 emit_expr(out, then_expr, mode)?;
919 write!(out, " }} else ")?;
920 match else_expr {
921 Expr::IfExpr {
922 cond: c2,
923 then_expr: t2,
924 else_expr: e2,
925 } => {
926 emit_if_expr(out, c2, t2, e2, mode)?;
927 return Ok(());
928 }
929 _ => {
930 write!(out, "{{ ")?;
931 emit_expr(out, else_expr, mode)?;
932 write!(out, " }}")?;
933 }
934 }
935 Ok(())
936}
937
938fn emit_binop(
954 out: &mut String,
955 op: BinOp,
956 lhs: &Expr,
957 rhs: &Expr,
958 mode: bool,
959) -> Result<(), CodegenError> {
960 match op {
961 BinOp::Add if mode => emit_infix(out, lhs, " + ", rhs, mode),
966 BinOp::Sub if mode => emit_infix(out, lhs, " - ", rhs, mode),
967 BinOp::Mul if mode => emit_infix(out, lhs, " * ", rhs, mode),
968 BinOp::FloorDiv if mode => emit_bigint_floor_call(out, "div_floor", lhs, rhs, mode),
969 BinOp::Mod if mode => emit_bigint_floor_call(out, "mod_floor", lhs, rhs, mode),
970 BinOp::BitAnd if mode => emit_infix(out, lhs, " & ", rhs, mode),
979 BinOp::BitOr if mode => emit_infix(out, lhs, " | ", rhs, mode),
980 BinOp::BitXor if mode => emit_infix(out, lhs, " ^ ", rhs, mode),
981 BinOp::Shl if mode => emit_bigint_floor_call(out, "shl", lhs, rhs, mode),
982 BinOp::Shr if mode => emit_bigint_floor_call(out, "shr", lhs, rhs, mode),
983 BinOp::Pow if mode => emit_bigint_floor_call(out, "pow", lhs, rhs, mode),
984 BinOp::Add => emit_checked(out, lhs, "checked_add", rhs, "addition", mode),
985 BinOp::Sub => emit_checked(out, lhs, "checked_sub", rhs, "subtraction", mode),
986 BinOp::Mul => emit_checked(out, lhs, "checked_mul", rhs, "multiplication", mode),
987 BinOp::FloorDiv => emit_checked(out, lhs, "checked_div_euclid", rhs, "floor-div", mode),
988 BinOp::Mod => emit_checked(out, lhs, "checked_rem_euclid", rhs, "modulo", mode),
989 BinOp::Eq => emit_infix(out, lhs, " == ", rhs, mode),
990 BinOp::NotEq => emit_infix(out, lhs, " != ", rhs, mode),
991 BinOp::Lt => emit_infix(out, lhs, " < ", rhs, mode),
992 BinOp::LtEq => emit_infix(out, lhs, " <= ", rhs, mode),
993 BinOp::Gt => emit_infix(out, lhs, " > ", rhs, mode),
994 BinOp::GtEq => emit_infix(out, lhs, " >= ", rhs, mode),
995 BinOp::And => emit_infix(out, lhs, " && ", rhs, mode),
996 BinOp::Or => emit_infix(out, lhs, " || ", rhs, mode),
997 BinOp::BitAnd => emit_infix(out, lhs, " & ", rhs, mode),
998 BinOp::BitOr => emit_infix(out, lhs, " | ", rhs, mode),
999 BinOp::BitXor => emit_infix(out, lhs, " ^ ", rhs, mode),
1000 BinOp::Shl => emit_checked_shift(out, lhs, "checked_shl", rhs, "left-shift", mode),
1001 BinOp::Shr => emit_checked_shift(out, lhs, "checked_shr", rhs, "right-shift", mode),
1002 BinOp::Pow => emit_checked_pow(out, lhs, rhs, mode),
1003 }
1004}
1005
1006fn emit_bigint_floor_call(
1010 out: &mut String,
1011 method: &str,
1012 lhs: &Expr,
1013 rhs: &Expr,
1014 mode: bool,
1015) -> Result<(), CodegenError> {
1016 write!(out, "xpile_bigint::{method}(&")?;
1017 emit_expr(out, lhs, mode)?;
1018 write!(out, ", &")?;
1019 emit_expr(out, rhs, mode)?;
1020 write!(out, ")")?;
1021 Ok(())
1022}
1023
1024fn emit_checked_pow(
1030 out: &mut String,
1031 lhs: &Expr,
1032 rhs: &Expr,
1033 mode: bool,
1034) -> Result<(), CodegenError> {
1035 write!(out, "(")?;
1036 emit_expr(out, lhs, mode)?;
1037 write!(out, ").checked_pow(u32::try_from(")?;
1038 emit_expr(out, rhs, mode)?;
1039 write!(
1040 out,
1041 ").expect(\"xpile: exponent out of range for u32 — Python returns Float for negative exponents which v0.1.0 cannot represent (contract C-PY-INT-ARITH)\")).expect(\"xpile: i64 power overflow; bigint promotion (contract C-PY-INT-ARITH slow path) not yet implemented\")"
1042 )?;
1043 Ok(())
1044}
1045
1046fn emit_checked_shift(
1051 out: &mut String,
1052 lhs: &Expr,
1053 method: &str,
1054 rhs: &Expr,
1055 op_name: &str,
1056 mode: bool,
1057) -> Result<(), CodegenError> {
1058 write!(out, "(")?;
1059 emit_expr(out, lhs, mode)?;
1060 write!(out, ").{method}(u32::try_from(")?;
1061 emit_expr(out, rhs, mode)?;
1062 write!(
1063 out,
1064 ").expect(\"xpile: shift amount out of range for u32 (contract C-PY-INT-ARITH)\")).expect(\"xpile: i64 {op_name} overflow; bigint promotion (contract C-PY-INT-ARITH slow path) not yet implemented\")"
1065 )?;
1066 Ok(())
1067}
1068
1069fn emit_checked(
1072 out: &mut String,
1073 lhs: &Expr,
1074 method: &str,
1075 rhs: &Expr,
1076 op_name: &str,
1077 mode: bool,
1078) -> Result<(), CodegenError> {
1079 write!(out, "(")?;
1080 emit_expr(out, lhs, mode)?;
1081 write!(out, ").{method}(")?;
1082 emit_expr(out, rhs, mode)?;
1083 write!(
1084 out,
1085 ").expect(\"xpile: i64 {op_name} overflow; bigint promotion (contract C-PY-INT-ARITH slow path) not yet implemented\")"
1086 )?;
1087 Ok(())
1088}
1089
1090fn emit_infix(
1091 out: &mut String,
1092 lhs: &Expr,
1093 op: &str,
1094 rhs: &Expr,
1095 mode: bool,
1096) -> Result<(), CodegenError> {
1097 write!(out, "(")?;
1098 emit_expr(out, lhs, mode)?;
1099 out.push_str(op);
1100 emit_expr(out, rhs, mode)?;
1101 write!(out, ")")?;
1102 Ok(())
1103}
1104
1105pub struct RustBackend;
1106
1107impl Backend for RustBackend {
1108 fn name(&self) -> &'static str {
1109 "rust"
1110 }
1111
1112 fn targets(&self) -> &[Target] {
1113 &[Target::Rust]
1114 }
1115
1116 fn lower(&self, module: &Module, _config: &BackendConfig) -> Result<Artifact, BackendError> {
1117 let primary = emit_module(module).map_err(|e| BackendError::Lower(e.to_string()))?;
1118 Ok(Artifact {
1119 primary,
1120 sidecars: Vec::new(),
1121 citations: Vec::new(),
1122 quorum_status: QuorumStatus::Single {
1123 emitter: "xpile-rust-codegen".to_string(),
1124 },
1125 })
1126 }
1127}
1128
1129fn emit_c_function(out: &mut String, f: &Function) -> Result<(), CodegenError> {
1139 writeln!(out, "// xpile-contract: C-C-INT-ARITH")?;
1142 write!(out, "pub fn {}(", f.name)?;
1143 for (i, p) in f.params.iter().enumerate() {
1144 if i > 0 {
1145 write!(out, ", ")?;
1146 }
1147 write!(out, "{}: i32", p.name)?;
1148 }
1149 writeln!(out, ") -> i32 {{")?;
1150 for stmt in &f.body.stmts {
1151 emit_c_stmt(out, stmt, " ")?;
1152 }
1153 write!(out, " ")?;
1154 emit_c_expr(out, &f.body.trailing_return)?;
1155 writeln!(out)?;
1156 writeln!(out, "}}")?;
1157 Ok(())
1158}
1159
1160fn emit_c_stmt(out: &mut String, stmt: &Stmt, indent: &str) -> Result<(), CodegenError> {
1161 match stmt {
1162 Stmt::Let {
1163 name,
1164 value,
1165 mutable,
1166 ..
1167 } => {
1168 let kw = if *mutable { "let mut" } else { "let" };
1169 write!(out, "{indent}{kw} {name}: i32 = ")?;
1170 emit_c_expr(out, value)?;
1171 writeln!(out, ";")?;
1172 Ok(())
1173 }
1174 Stmt::Assign { name, value } => {
1175 write!(out, "{indent}{name} = ")?;
1176 emit_c_expr(out, value)?;
1177 writeln!(out, ";")?;
1178 Ok(())
1179 }
1180 Stmt::Return(e) => {
1182 write!(out, "{indent}return ")?;
1183 emit_c_expr(out, e)?;
1184 writeln!(out, ";")?;
1185 Ok(())
1186 }
1187 Stmt::While { cond, body } => {
1188 write!(out, "{indent}while ")?;
1189 emit_c_expr(out, cond)?;
1190 writeln!(out, " {{")?;
1191 let inner = format!("{indent} ");
1192 for s in body {
1193 emit_c_stmt(out, s, &inner)?;
1194 }
1195 writeln!(out, "{indent}}}")?;
1196 Ok(())
1197 }
1198 Stmt::If {
1201 cond,
1202 then_body,
1203 else_body,
1204 } => {
1205 write!(out, "{indent}if ")?;
1206 emit_c_expr(out, cond)?;
1207 writeln!(out, " {{")?;
1208 let inner = format!("{indent} ");
1209 for s in then_body {
1210 emit_c_stmt(out, s, &inner)?;
1211 }
1212 if else_body.is_empty() {
1213 writeln!(out, "{indent}}}")?;
1214 } else {
1215 writeln!(out, "{indent}}} else {{")?;
1216 for s in else_body {
1217 emit_c_stmt(out, s, &inner)?;
1218 }
1219 writeln!(out, "{indent}}}")?;
1220 }
1221 Ok(())
1222 }
1223 other => Err(CodegenError::Unsupported(format!(
1224 "C backend supports `int x = e;`, `x = e;`, `if (c) {{ … }} else {{ … }}`, and `while (c) {{ … }}`, got {other:?}"
1225 ))),
1226 }
1227}
1228
1229fn emit_c_expr(out: &mut String, e: &Expr) -> Result<(), CodegenError> {
1230 match e {
1231 Expr::LitInt(v) => write!(out, "{v}i32")?,
1232 Expr::Ident(name) => write!(out, "{name}")?,
1233 Expr::BinOp { op, lhs, rhs } => emit_c_binop(out, *op, lhs, rhs)?,
1234 Expr::UnOp { op, operand } => match op {
1235 UnOp::Neg => {
1238 write!(out, "(")?;
1239 emit_c_expr(out, operand)?;
1240 write!(out, ").wrapping_neg()")?;
1241 }
1242 UnOp::Not => {
1243 write!(out, "!(")?;
1244 emit_c_expr(out, operand)?;
1245 write!(out, ")")?;
1246 }
1247 },
1248 Expr::IfExpr {
1249 cond,
1250 then_expr,
1251 else_expr,
1252 } => {
1253 write!(out, "if ")?;
1254 emit_c_expr(out, cond)?;
1255 write!(out, " {{ ")?;
1256 emit_c_expr(out, then_expr)?;
1257 write!(out, " }} else {{ ")?;
1258 emit_c_expr(out, else_expr)?;
1259 write!(out, " }}")?;
1260 }
1261 Expr::Call { callee, args } => {
1262 write!(out, "{callee}(")?;
1263 for (i, a) in args.iter().enumerate() {
1264 if i > 0 {
1265 write!(out, ", ")?;
1266 }
1267 emit_c_expr(out, a)?;
1268 }
1269 write!(out, ")")?;
1270 }
1271 other => {
1272 return Err(CodegenError::Unsupported(format!(
1273 "C backend slice 1 does not lower {other:?} — supported: int literals, \
1274 identifiers, calls, + - *, comparisons, && ||, unary - !, and the ternary"
1275 )));
1276 }
1277 }
1278 Ok(())
1279}
1280
1281fn emit_c_binop(out: &mut String, op: BinOp, lhs: &Expr, rhs: &Expr) -> Result<(), CodegenError> {
1282 let wrapping = |out: &mut String, method: &str| -> Result<(), CodegenError> {
1287 write!(out, "(")?;
1288 emit_c_expr(out, lhs)?;
1289 write!(out, ").{method}(")?;
1290 emit_c_expr(out, rhs)?;
1291 write!(out, ")")?;
1292 Ok(())
1293 };
1294 let infix = |out: &mut String, sym: &str| -> Result<(), CodegenError> {
1295 emit_c_expr(out, lhs)?;
1296 write!(out, " {sym} ")?;
1297 emit_c_expr(out, rhs)?;
1298 Ok(())
1299 };
1300 match op {
1301 BinOp::Add => wrapping(out, "wrapping_add")?,
1302 BinOp::Sub => wrapping(out, "wrapping_sub")?,
1303 BinOp::Mul => wrapping(out, "wrapping_mul")?,
1304 BinOp::FloorDiv => wrapping(out, "wrapping_div")?,
1310 BinOp::Mod => wrapping(out, "wrapping_rem")?,
1311 BinOp::Eq => infix(out, "==")?,
1312 BinOp::NotEq => infix(out, "!=")?,
1313 BinOp::Lt => infix(out, "<")?,
1314 BinOp::LtEq => infix(out, "<=")?,
1315 BinOp::Gt => infix(out, ">")?,
1316 BinOp::GtEq => infix(out, ">=")?,
1317 BinOp::And => infix(out, "&&")?,
1318 BinOp::Or => infix(out, "||")?,
1319 other => {
1320 return Err(CodegenError::Unsupported(format!(
1321 "C backend slice 1 does not lower BinOp::{other:?} — `/`, `%`, bitwise, \
1322 shift, and power are deferred to a later decy slice"
1323 )));
1324 }
1325 }
1326 Ok(())
1327}
1328
1329#[cfg(test)]
1330mod tests {
1331 use super::*;
1332 use xpile_meta_hir::{Module, SourceLang};
1333
1334 fn module_with(name: &str, items: Vec<Item>) -> Module {
1335 Module {
1336 name: name.into(),
1337 source_lang: SourceLang::Python,
1338 items,
1339 ffi_boundaries: Vec::new(),
1340 }
1341 }
1342
1343 fn add_fn() -> Function {
1344 Function {
1345 name: "add".into(),
1346 params: vec![
1347 Param {
1348 name: "a".into(),
1349 ty: Type::I64,
1350 mutable: false,
1351 },
1352 Param {
1353 name: "b".into(),
1354 ty: Type::I64,
1355 mutable: false,
1356 },
1357 ],
1358 return_type: Type::I64,
1359 body: Block {
1360 stmts: vec![],
1361 trailing_return: Expr::BinOp {
1362 op: BinOp::Add,
1363 lhs: Box::new(Expr::Ident("a".into())),
1364 rhs: Box::new(Expr::Ident("b".into())),
1365 },
1366 },
1367 }
1368 }
1369
1370 #[test]
1371 fn emits_add_function() {
1372 let m = module_with("fixture", vec![Item::Function(add_fn())]);
1373 let rust = emit_module(&m).expect("emit ok");
1374 assert!(rust.contains("pub fn add(a: i64, b: i64) -> i64"));
1375 assert!(rust.contains("checked_add"), "expected checked_add: {rust}");
1380 assert!(
1381 rust.contains("C-PY-INT-ARITH"),
1382 "expected contract reference in panic msg: {rust}"
1383 );
1384 }
1385
1386 #[test]
1387 fn emits_floordiv_as_div_euclid() {
1388 let f = Function {
1390 name: "fdiv".into(),
1391 params: vec![
1392 Param {
1393 name: "a".into(),
1394 ty: Type::I64,
1395 mutable: false,
1396 },
1397 Param {
1398 name: "b".into(),
1399 ty: Type::I64,
1400 mutable: false,
1401 },
1402 ],
1403 return_type: Type::I64,
1404 body: Block {
1405 stmts: vec![],
1406 trailing_return: Expr::BinOp {
1407 op: BinOp::FloorDiv,
1408 lhs: Box::new(Expr::Ident("a".into())),
1409 rhs: Box::new(Expr::Ident("b".into())),
1410 },
1411 },
1412 };
1413 let m = module_with("fixture", vec![Item::Function(f)]);
1414 let rust = emit_module(&m).expect("emit ok");
1415 assert!(
1416 rust.contains("div_euclid"),
1417 "Python floor-div must lower to div_euclid (got: {})",
1418 rust
1419 );
1420 assert!(
1421 !rust.contains(" / "),
1422 "must not use plain Rust `/` for Python `//`"
1423 );
1424 }
1425
1426 #[test]
1427 fn emits_comparison_returning_bool() {
1428 let f = Function {
1429 name: "le".into(),
1430 params: vec![
1431 Param {
1432 name: "a".into(),
1433 ty: Type::I64,
1434 mutable: false,
1435 },
1436 Param {
1437 name: "b".into(),
1438 ty: Type::I64,
1439 mutable: false,
1440 },
1441 ],
1442 return_type: Type::Bool,
1443 body: Block {
1444 stmts: vec![],
1445 trailing_return: Expr::BinOp {
1446 op: BinOp::LtEq,
1447 lhs: Box::new(Expr::Ident("a".into())),
1448 rhs: Box::new(Expr::Ident("b".into())),
1449 },
1450 },
1451 };
1452 let m = module_with("fixture", vec![Item::Function(f)]);
1453 let rust = emit_module(&m).expect("emit ok");
1454 assert!(rust.contains("-> bool"));
1455 assert!(rust.contains("(a <= b)"));
1456 }
1457
1458 #[test]
1459 fn emits_if_expression_for_ternary() {
1460 let f = Function {
1461 name: "pick".into(),
1462 params: vec![
1463 Param {
1464 name: "a".into(),
1465 ty: Type::I64,
1466 mutable: false,
1467 },
1468 Param {
1469 name: "b".into(),
1470 ty: Type::I64,
1471 mutable: false,
1472 },
1473 ],
1474 return_type: Type::I64,
1475 body: Block {
1476 stmts: vec![],
1477 trailing_return: Expr::IfExpr {
1478 cond: Box::new(Expr::BinOp {
1479 op: BinOp::LtEq,
1480 lhs: Box::new(Expr::Ident("a".into())),
1481 rhs: Box::new(Expr::Ident("b".into())),
1482 }),
1483 then_expr: Box::new(Expr::Ident("a".into())),
1484 else_expr: Box::new(Expr::Ident("b".into())),
1485 },
1486 },
1487 };
1488 let m = module_with("fixture", vec![Item::Function(f)]);
1489 let rust = emit_module(&m).expect("emit ok");
1490 assert!(rust.contains("if (a <= b) { a } else { b }"));
1491 assert!(rust.contains("pub fn pick(a: i64, b: i64) -> i64"));
1492 }
1493
1494 #[test]
1495 fn emit_module_produces_rustc_parseable_output() {
1496 let m = module_with("fixture", vec![Item::Function(add_fn())]);
1501 let rust = emit_module(&m).expect("emit ok");
1502 assert_eq!(rust.matches('{').count(), rust.matches('}').count());
1504 assert!(rust.ends_with('\n'));
1505 }
1506}