1use std::fmt::Write;
14use xpile_backend::{Artifact, Backend, BackendConfig, BackendError, QuorumStatus, Target};
15use xpile_meta_hir::{
16 BinOp, Block, Expr, FloatOp, Function, Item, Module, NumBuiltinOp, Param, Stmt, StrMethodOp,
17 Type, UnOp,
18};
19
20fn float_op_sym(op: FloatOp) -> &'static str {
22 match op {
23 FloatOp::Add => "+",
24 FloatOp::Sub => "-",
25 FloatOp::Mul => "*",
26 FloatOp::Div => "/",
27 }
28}
29
30#[derive(Debug, thiserror::Error)]
31pub enum RuchyCodegenError {
32 #[error("unsupported item: {0}")]
33 Unsupported(String),
34 #[error("formatting error: {0}")]
35 Format(#[from] std::fmt::Error),
36}
37
38pub fn emit_module(module: &Module) -> Result<String, RuchyCodegenError> {
39 let mut out = String::new();
40 writeln!(
41 out,
42 "// xpile-generated from {:?} module {}",
43 module.source_lang, module.name
44 )?;
45 writeln!(out)?;
46 for item in &module.items {
47 match item {
48 Item::Function(f) => emit_function(&mut out, f)?,
49 }
50 }
51 Ok(out)
52}
53
54fn emit_function(out: &mut String, f: &Function) -> Result<(), RuchyCodegenError> {
55 emit_contract_citations(out, f)?;
56 write!(out, "fun {}(", f.name)?;
58 for (i, p) in f.params.iter().enumerate() {
59 if i > 0 {
60 write!(out, ", ")?;
61 }
62 emit_param(out, p)?;
63 }
64 write!(out, ") -> ")?;
65 emit_type(out, &f.return_type)?;
66 writeln!(out, " {{")?;
67 let mode = function_bigint_mode(f);
68 emit_block(out, &f.body, mode)?;
69 writeln!(out, "}}")?;
70 Ok(())
71}
72
73fn function_bigint_mode(f: &Function) -> bool {
80 if matches!(f.return_type, Type::BigInt) {
81 return true;
82 }
83 if f.params.iter().any(|p| matches!(p.ty, Type::BigInt)) {
84 return true;
85 }
86 fn stmt_has_bigint(s: &Stmt) -> bool {
87 match s {
88 Stmt::Let { ty, .. } => matches!(ty, Type::BigInt),
89 Stmt::Assign { .. }
93 | Stmt::Assert { .. }
94 | Stmt::Return(_)
95 | Stmt::LetTuple { .. }
96 | Stmt::Raise { .. } => false,
97 Stmt::While { body, .. }
98 | Stmt::ForEach { body, .. }
99 | Stmt::ForEachPair { body, .. } => body.iter().any(stmt_has_bigint),
100 Stmt::If {
102 then_body,
103 else_body,
104 ..
105 } => then_body.iter().any(stmt_has_bigint) || else_body.iter().any(stmt_has_bigint),
106 Stmt::ListAppend { .. } | Stmt::SetAdd { .. } => false,
108 Stmt::IndexAssign { .. } => false,
110 Stmt::DictSet { .. } => false,
112 Stmt::Cmd { .. } => false,
115 Stmt::Pipeline { .. } => false,
117 Stmt::ShellLoop { .. } => false,
119 Stmt::ShellAssign { .. } => false,
121 }
122 }
123 f.body.stmts.iter().any(stmt_has_bigint)
124}
125
126fn emit_contract_citations(out: &mut String, f: &Function) -> Result<(), RuchyCodegenError> {
129 for id in f.applicable_contracts() {
130 writeln!(out, "// xpile-contract: {id}")?;
131 }
132 Ok(())
133}
134
135fn emit_block(out: &mut String, block: &Block, mode: bool) -> Result<(), RuchyCodegenError> {
136 for stmt in &block.stmts {
137 emit_stmt(out, stmt, mode)?;
138 }
139 write!(out, " ")?;
140 emit_expr(out, &block.trailing_return, mode)?;
141 writeln!(out)?;
142 Ok(())
143}
144
145fn emit_stmt(out: &mut String, stmt: &Stmt, mode: bool) -> Result<(), RuchyCodegenError> {
146 emit_stmt_indented(out, stmt, " ", mode)
147}
148
149fn emit_stmt_indented(
150 out: &mut String,
151 stmt: &Stmt,
152 indent: &str,
153 mode: bool,
154) -> Result<(), RuchyCodegenError> {
155 match stmt {
156 Stmt::Let {
157 name,
158 ty,
159 value,
160 mutable,
161 } => {
162 let kw = if *mutable { "let mut" } else { "let" };
163 write!(out, "{indent}{kw} {name}: ")?;
164 emit_type(out, ty)?;
165 write!(out, " = ")?;
166 emit_expr(out, value, mode)?;
167 writeln!(out, ";")?;
168 Ok(())
169 }
170 Stmt::LetTuple { names, value } => {
172 write!(out, "{indent}let ({}) = ", names.join(", "))?;
173 emit_expr(out, value, mode)?;
174 writeln!(out, ";")?;
175 Ok(())
176 }
177 Stmt::Return(e) => {
179 write!(out, "{indent}return ")?;
180 emit_expr(out, e, mode)?;
181 writeln!(out, ";")?;
182 Ok(())
183 }
184 Stmt::If {
186 cond,
187 then_body,
188 else_body,
189 } => {
190 write!(out, "{indent}if ")?;
191 emit_expr(out, cond, mode)?;
192 writeln!(out, " {{")?;
193 let inner = format!("{indent} ");
194 for s in then_body {
195 emit_stmt_indented(out, s, &inner, mode)?;
196 }
197 if else_body.is_empty() {
198 writeln!(out, "{indent}}}")?;
199 } else {
200 writeln!(out, "{indent}}} else {{")?;
201 for s in else_body {
202 emit_stmt_indented(out, s, &inner, mode)?;
203 }
204 writeln!(out, "{indent}}}")?;
205 }
206 Ok(())
207 }
208 Stmt::Assign { name, value } => {
209 write!(out, "{indent}{name} = ")?;
210 emit_expr(out, value, mode)?;
211 writeln!(out, ";")?;
212 Ok(())
213 }
214 Stmt::While { cond, body } => {
215 write!(out, "{indent}while ")?;
216 emit_expr(out, cond, mode)?;
217 writeln!(out, " {{")?;
218 let inner = format!("{indent} ");
219 for s in body {
220 emit_stmt_indented(out, s, &inner, mode)?;
221 }
222 writeln!(out, "{indent}}}")?;
223 Ok(())
224 }
225 Stmt::ForEach {
228 var,
229 iter,
230 body,
231 over_keys,
232 ..
233 } => {
234 let method = if *over_keys { "keys" } else { "iter" };
236 write!(out, "{indent}for {var} in ")?;
237 emit_expr(out, iter, mode)?;
238 writeln!(out, ".{method}().cloned() {{")?;
239 let inner = format!("{indent} ");
240 for s in body {
241 emit_stmt_indented(out, s, &inner, mode)?;
242 }
243 writeln!(out, "{indent}}}")?;
244 Ok(())
245 }
246 Stmt::ForEachPair {
248 first,
249 second,
250 iter,
251 kind,
252 body,
253 } => {
254 write!(out, "{indent}for ({first}, {second}) in ")?;
255 emit_expr(out, iter, mode)?;
256 match kind {
257 xpile_meta_hir::PairIterKind::Enumerate => {
258 out.push_str(
259 ".iter().cloned().enumerate().map(|(__i, __e)| (__i as i64, __e))",
260 );
261 }
262 xpile_meta_hir::PairIterKind::Zip(other) => {
263 out.push_str(".iter().cloned().zip(");
264 emit_expr(out, other, mode)?;
265 out.push_str(".iter().cloned())");
266 }
267 }
268 writeln!(out, " {{")?;
269 let inner = format!("{indent} ");
270 for s in body {
271 emit_stmt_indented(out, s, &inner, mode)?;
272 }
273 writeln!(out, "{indent}}}")?;
274 Ok(())
275 }
276 Stmt::ListAppend { list_name, elem } => {
278 write!(out, "{indent}{list_name}.push(")?;
279 emit_expr(out, elem, mode)?;
280 writeln!(out, ");")?;
281 Ok(())
282 }
283 Stmt::SetAdd { set_name, elem } => {
285 write!(out, "{indent}{set_name}.insert(")?;
286 emit_expr(out, elem, mode)?;
287 writeln!(out, ");")?;
288 Ok(())
289 }
290 Stmt::IndexAssign {
293 list_name,
294 index,
295 value,
296 } => {
297 write!(out, "{indent}{list_name}[")?;
298 emit_expr(out, index, mode)?;
299 out.push_str(" as usize] = ");
300 emit_expr(out, value, mode)?;
301 writeln!(out, ";")?;
302 Ok(())
303 }
304 Stmt::DictSet {
310 dict_name,
311 key,
312 value,
313 } => {
314 write!(out, "{indent}{{ let __xpile_dict_val = ")?;
315 emit_expr(out, value, mode)?;
316 write!(out, "; {dict_name}.insert(")?;
317 emit_expr(out, key, mode)?;
318 writeln!(out, ".clone(), __xpile_dict_val); }}")?;
319 Ok(())
320 }
321 Stmt::Assert { cond } => {
322 write!(out, "{indent}assert!(")?;
323 emit_expr(out, cond, mode)?;
324 writeln!(out, ");")?;
325 Ok(())
326 }
327 Stmt::Raise { message } => {
330 write!(out, "{indent}panic!(\"{{}}\", ")?;
331 emit_expr(out, message, mode)?;
332 writeln!(out, ");")?;
333 Ok(())
334 }
335 Stmt::Cmd { program, args } => Err(RuchyCodegenError::Unsupported(format!(
340 "Ruchy backend does not lower Stmt::Cmd (`{program}` with {} arg(s)) — \
341 contract C-BASHRS-POSIX-IDEMPOTENCE governs this construct; \
342 use `--target shell` to emit POSIX sh via bashrs-backend",
343 args.len()
344 ))),
345 Stmt::Pipeline { stages } => Err(RuchyCodegenError::Unsupported(format!(
347 "Ruchy backend does not lower Stmt::Pipeline ({} stages) — \
348 contract C-BASHRS-POSIX-IDEMPOTENCE governs shell pipelines; \
349 use `--target shell`",
350 stages.len()
351 ))),
352 Stmt::ShellLoop { .. } => Err(RuchyCodegenError::Unsupported(
354 "Ruchy backend does not lower Stmt::ShellLoop — \
355 contract C-BASHRS-POSIX-IDEMPOTENCE governs shell loops; \
356 use `--target shell`"
357 .into(),
358 )),
359 Stmt::ShellAssign { name, .. } => Err(RuchyCodegenError::Unsupported(format!(
361 "Ruchy backend does not lower Stmt::ShellAssign (`{name}=…`) — \
362 contract C-BASHRS-POSIX-IDEMPOTENCE governs shell variable assignment; \
363 use `--target shell`"
364 ))),
365 }
366}
367
368fn emit_param(out: &mut String, p: &Param) -> Result<(), RuchyCodegenError> {
369 if p.mutable {
371 write!(out, "mut ")?;
372 }
373 write!(out, "{}: ", p.name)?;
374 emit_type(out, &p.ty)?;
375 Ok(())
376}
377
378fn escape_ruchy_str(s: &str) -> String {
381 let mut out = String::with_capacity(s.len());
382 for c in s.chars() {
383 match c {
384 '\\' => out.push_str("\\\\"),
385 '"' => out.push_str("\\\""),
386 other => out.push(other),
387 }
388 }
389 out
390}
391
392fn emit_type(out: &mut String, t: &Type) -> Result<(), RuchyCodegenError> {
393 match t {
394 Type::I64 => out.push_str("i64"),
395 Type::F64 => out.push_str("f64"),
397 Type::Bool => out.push_str("bool"),
398 Type::BigInt => out.push_str("xpile_bigint::BigInt"),
400 Type::Str => out.push_str("String"),
403 Type::List(elem_ty) => {
405 out.push_str("Vec<");
406 emit_type(out, elem_ty)?;
407 out.push('>');
408 }
409 Type::Dict(k_ty, v_ty) => {
411 out.push_str("std::collections::HashMap<");
412 emit_type(out, k_ty)?;
413 out.push_str(", ");
414 emit_type(out, v_ty)?;
415 out.push('>');
416 }
417 Type::Set(elem_ty) => {
419 out.push_str("std::collections::HashSet<");
420 emit_type(out, elem_ty)?;
421 out.push('>');
422 }
423 Type::Tuple(elems) => {
425 out.push('(');
426 for (i, t) in elems.iter().enumerate() {
427 if i > 0 {
428 out.push_str(", ");
429 }
430 emit_type(out, t)?;
431 }
432 out.push(')');
433 }
434 Type::ShellString | Type::ExitCode => {
436 return Err(RuchyCodegenError::Unsupported(format!(
437 "Ruchy backend does not lower {t:?} — \
438 contract C-BASHRS-POSIX-IDEMPOTENCE governs the bashrs type domain; \
439 use `--target shell`"
440 )));
441 }
442 }
443 Ok(())
444}
445
446fn emit_expr(out: &mut String, e: &Expr, mode: bool) -> Result<(), RuchyCodegenError> {
447 match e {
448 Expr::Ident(name) => {
449 if mode {
455 write!(out, "{}.clone()", name)?;
456 } else {
457 write!(out, "{}", name)?;
458 }
459 }
460 Expr::LitInt(v) => {
461 if mode {
462 write!(out, "xpile_bigint::BigInt::from({}i64)", v)?;
463 } else {
464 write!(out, "{}i64", v)?;
465 }
466 }
467 Expr::LitFloat(v) => write!(out, "{}f64", v)?,
469 Expr::FloatBinOp { op, lhs, rhs } => {
470 out.push('(');
471 emit_expr(out, lhs, mode)?;
472 write!(out, " {} ", float_op_sym(*op))?;
473 emit_expr(out, rhs, mode)?;
474 out.push(')');
475 }
476 Expr::LitBool(b) => write!(out, "{}", b)?,
479 Expr::BinOp { op, lhs, rhs } => emit_binop(out, *op, lhs, rhs, mode)?,
480 Expr::Concat { lhs, rhs } => {
484 out.push_str("format!(\"{}{}\", ");
485 emit_expr(out, lhs, mode)?;
486 out.push_str(", ");
487 emit_expr(out, rhs, mode)?;
488 out.push(')');
489 }
490 Expr::StrMethod { recv, op, args } => {
494 if matches!(op, StrMethodOp::Join) {
496 emit_expr(out, &args[0], mode)?;
497 out.push_str(".join(&(");
498 emit_expr(out, recv, mode)?;
499 out.push_str(")[..])");
500 return Ok(());
501 }
502 emit_expr(out, recv, mode)?;
503 match op {
504 StrMethodOp::Upper => out.push_str(".to_uppercase()"),
505 StrMethodOp::Lower => out.push_str(".to_lowercase()"),
506 StrMethodOp::Strip => out.push_str(".trim().to_string()"),
507 StrMethodOp::StartsWith | StrMethodOp::EndsWith => {
508 out.push_str(if matches!(op, StrMethodOp::StartsWith) {
509 ".starts_with(&("
510 } else {
511 ".ends_with(&("
512 });
513 emit_expr(out, &args[0], mode)?;
514 out.push_str(")[..])");
515 }
516 StrMethodOp::Split => {
518 out.push_str(".split(&(");
519 emit_expr(out, &args[0], mode)?;
520 out.push_str(")[..]).map(|__c| __c.to_string()).collect::<Vec<String>>()");
521 }
522 StrMethodOp::Replace => {
524 out.push_str(".replace(&(");
525 emit_expr(out, &args[0], mode)?;
526 out.push_str(")[..], &(");
527 emit_expr(out, &args[1], mode)?;
528 out.push_str(")[..])");
529 }
530 StrMethodOp::Join => unreachable!("Join handled above"),
531 }
532 }
533 Expr::ListLit(elems) => {
535 out.push_str("vec![");
536 for (i, e) in elems.iter().enumerate() {
537 if i > 0 {
538 out.push_str(", ");
539 }
540 emit_expr(out, e, mode)?;
541 }
542 out.push(']');
543 }
544 Expr::TupleLit(elems) => {
546 out.push('(');
547 for (i, e) in elems.iter().enumerate() {
548 if i > 0 {
549 out.push_str(", ");
550 }
551 emit_expr(out, e, mode)?;
552 }
553 out.push(')');
554 }
555 Expr::Slice {
557 collection,
558 lo,
559 hi,
560 of_str,
561 } => {
562 emit_expr(out, collection, mode)?;
563 out.push_str("[(");
564 emit_expr(out, lo, mode)?;
565 out.push_str(") as usize..(");
566 emit_expr(out, hi, mode)?;
567 out.push_str(") as usize]");
568 out.push_str(if *of_str { ".to_string()" } else { ".to_vec()" });
569 }
570 Expr::NumBuiltin { op, args } => {
572 out.push('(');
573 emit_expr(out, &args[0], mode)?;
574 out.push(')');
575 match op {
576 NumBuiltinOp::Abs => out.push_str(".abs()"),
577 NumBuiltinOp::Min | NumBuiltinOp::Max => {
578 out.push_str(if matches!(op, NumBuiltinOp::Min) {
579 ".min("
580 } else {
581 ".max("
582 });
583 emit_expr(out, &args[1], mode)?;
584 out.push(')');
585 }
586 }
587 }
588 Expr::Sum { list, of_float } => {
590 emit_expr(out, list, mode)?;
591 out.push_str(if *of_float {
592 ".iter().sum::<f64>()"
593 } else {
594 ".iter().sum::<i64>()"
595 });
596 }
597 Expr::ListMinMax { list, is_max } => {
599 emit_expr(out, list, mode)?;
600 out.push_str(if *is_max {
601 ".iter().copied().max().unwrap()"
602 } else {
603 ".iter().copied().min().unwrap()"
604 });
605 }
606 Expr::Sorted { list } => {
608 out.push_str("{ let mut __xv = ");
609 emit_expr(out, list, mode)?;
610 out.push_str(".clone(); __xv.sort(); __xv }");
611 }
612 Expr::Reversed { list } => {
614 out.push_str("{ let mut __xv = ");
615 emit_expr(out, list, mode)?;
616 out.push_str(".clone(); __xv.reverse(); __xv }");
617 }
618 Expr::DictLit(pairs) => {
622 if pairs.is_empty() {
623 out.push_str("std::collections::HashMap::new()");
624 } else {
625 out.push_str("{ let mut m = std::collections::HashMap::new(); ");
626 for (k, v) in pairs {
627 out.push_str("m.insert(");
628 emit_expr(out, k, mode)?;
629 out.push_str(", ");
630 emit_expr(out, v, mode)?;
631 out.push_str("); ");
632 }
633 out.push_str("m }");
634 }
635 }
636 Expr::Index { collection, index } => {
639 emit_expr(out, collection, mode)?;
640 out.push('[');
641 emit_expr(out, index, mode)?;
642 out.push_str(" as usize].clone()");
643 }
644 Expr::DictGet { dict, key } => {
647 emit_expr(out, dict, mode)?;
648 out.push_str("[&(");
649 emit_expr(out, key, mode)?;
650 out.push_str(")].clone()");
651 }
652 Expr::DictGetOr { dict, key, default } => {
653 emit_expr(out, dict, mode)?;
654 out.push_str(".get(&(");
655 emit_expr(out, key, mode)?;
656 out.push_str(")).cloned().unwrap_or(");
657 emit_expr(out, default, mode)?;
658 out.push(')');
659 }
660 Expr::DictContains { dict, key } => {
661 emit_expr(out, dict, mode)?;
662 out.push_str(".contains_key(&(");
663 emit_expr(out, key, mode)?;
664 out.push_str("))");
665 }
666 Expr::SetLit(elems) => {
668 if elems.is_empty() {
669 out.push_str("std::collections::HashSet::new()");
670 } else {
671 out.push_str("{ let mut __xset = std::collections::HashSet::new(); ");
672 for e in elems {
673 out.push_str("__xset.insert(");
674 emit_expr(out, e, mode)?;
675 out.push_str("); ");
676 }
677 out.push_str("__xset }");
678 }
679 }
680 Expr::SetContains { set, elem } => {
681 emit_expr(out, set, mode)?;
682 out.push_str(".contains(&(");
683 emit_expr(out, elem, mode)?;
684 out.push_str("))");
685 }
686 Expr::Len(inner) => {
688 emit_expr(out, inner, mode)?;
689 out.push_str(".len() as i64");
690 }
691 Expr::IfExpr {
692 cond,
693 then_expr,
694 else_expr,
695 } => emit_if_expr(out, cond, then_expr, else_expr, mode)?,
696 Expr::Call { callee, args } => emit_call(out, callee, args, mode)?,
697 Expr::UnOp { op, operand } => emit_unop(out, *op, operand, mode)?,
698 Expr::LitStr(s) => {
702 write!(out, "String::from(\"{}\")", escape_ruchy_str(s))?;
703 }
704 Expr::QuotedString { .. } => {
707 return Err(RuchyCodegenError::Unsupported(
708 "Ruchy backend does not lower Expr::QuotedString — \
709 contract C-BASHRS-POSIX-IDEMPOTENCE governs quoted shell strings; \
710 use `--target shell`"
711 .into(),
712 ));
713 }
714 Expr::ShellVar(name) => {
716 return Err(RuchyCodegenError::Unsupported(format!(
717 "Ruchy backend does not lower Expr::ShellVar (${name}) — \
718 contract C-BASHRS-POSIX-IDEMPOTENCE governs shell variable refs; \
719 use `--target shell`"
720 )));
721 }
722 Expr::CommandSubstitution(_) => {
724 return Err(RuchyCodegenError::Unsupported(
725 "Ruchy backend does not lower Expr::CommandSubstitution — \
726 contract C-BASHRS-POSIX-IDEMPOTENCE governs shell substitution; \
727 use `--target shell`"
728 .into(),
729 ));
730 }
731 Expr::ShellSpecial(name) => {
733 return Err(RuchyCodegenError::Unsupported(format!(
734 "Ruchy backend does not lower Expr::ShellSpecial (${name}) — \
735 contract C-BASHRS-POSIX-IDEMPOTENCE governs shell special params; \
736 use `--target shell`"
737 )));
738 }
739 }
740 Ok(())
741}
742
743fn emit_unop(
744 out: &mut String,
745 op: UnOp,
746 operand: &Expr,
747 mode: bool,
748) -> Result<(), RuchyCodegenError> {
749 match op {
750 UnOp::Neg => {
751 if mode {
752 write!(out, "(-")?;
754 emit_expr(out, operand, mode)?;
755 write!(out, ")")?;
756 } else {
757 write!(out, "(")?;
761 emit_expr(out, operand, mode)?;
762 write!(
763 out,
764 ").checked_neg().expect(\"xpile: i64 negation overflow; bigint promotion (contract C-PY-INT-ARITH slow path) not yet implemented\")"
765 )?;
766 }
767 }
768 UnOp::Not => {
769 write!(out, "(!")?;
770 emit_expr(out, operand, mode)?;
771 write!(out, ")")?;
772 }
773 }
774 Ok(())
775}
776
777fn emit_call(
778 out: &mut String,
779 callee: &str,
780 args: &[Expr],
781 mode: bool,
782) -> Result<(), RuchyCodegenError> {
783 write!(out, "{}(", callee)?;
784 for (i, a) in args.iter().enumerate() {
785 if i > 0 {
786 write!(out, ", ")?;
787 }
788 emit_expr(out, a, mode)?;
789 }
790 write!(out, ")")?;
791 Ok(())
792}
793
794fn emit_if_expr(
797 out: &mut String,
798 cond: &Expr,
799 then_expr: &Expr,
800 else_expr: &Expr,
801 mode: bool,
802) -> Result<(), RuchyCodegenError> {
803 write!(out, "if ")?;
804 emit_expr(out, cond, mode)?;
805 write!(out, " {{ ")?;
806 emit_expr(out, then_expr, mode)?;
807 write!(out, " }} else ")?;
808 match else_expr {
809 Expr::IfExpr {
810 cond: c2,
811 then_expr: t2,
812 else_expr: e2,
813 } => emit_if_expr(out, c2, t2, e2, mode),
814 _ => {
815 write!(out, "{{ ")?;
816 emit_expr(out, else_expr, mode)?;
817 write!(out, " }}")?;
818 Ok(())
819 }
820 }
821}
822
823fn emit_binop(
835 out: &mut String,
836 op: BinOp,
837 lhs: &Expr,
838 rhs: &Expr,
839 mode: bool,
840) -> Result<(), RuchyCodegenError> {
841 match op {
842 BinOp::Add if mode => emit_infix(out, lhs, " + ", rhs, mode),
843 BinOp::Sub if mode => emit_infix(out, lhs, " - ", rhs, mode),
844 BinOp::Mul if mode => emit_infix(out, lhs, " * ", rhs, mode),
845 BinOp::FloorDiv if mode => emit_bigint_floor_call(out, "div_floor", lhs, rhs, mode),
846 BinOp::Mod if mode => emit_bigint_floor_call(out, "mod_floor", lhs, rhs, mode),
847 BinOp::BitAnd if mode => emit_infix(out, lhs, " & ", rhs, mode),
850 BinOp::BitOr if mode => emit_infix(out, lhs, " | ", rhs, mode),
851 BinOp::BitXor if mode => emit_infix(out, lhs, " ^ ", rhs, mode),
852 BinOp::Shl if mode => emit_bigint_floor_call(out, "shl", lhs, rhs, mode),
853 BinOp::Shr if mode => emit_bigint_floor_call(out, "shr", lhs, rhs, mode),
854 BinOp::Pow if mode => emit_bigint_floor_call(out, "pow", lhs, rhs, mode),
855 BinOp::Add => emit_checked(out, lhs, "checked_add", rhs, "addition", mode),
856 BinOp::Sub => emit_checked(out, lhs, "checked_sub", rhs, "subtraction", mode),
857 BinOp::Mul => emit_checked(out, lhs, "checked_mul", rhs, "multiplication", mode),
858 BinOp::FloorDiv => emit_checked(out, lhs, "checked_div_euclid", rhs, "floor-div", mode),
859 BinOp::Mod => emit_checked(out, lhs, "checked_rem_euclid", rhs, "modulo", mode),
860 BinOp::Eq => emit_infix(out, lhs, " == ", rhs, mode),
861 BinOp::NotEq => emit_infix(out, lhs, " != ", rhs, mode),
862 BinOp::Lt => emit_infix(out, lhs, " < ", rhs, mode),
863 BinOp::LtEq => emit_infix(out, lhs, " <= ", rhs, mode),
864 BinOp::Gt => emit_infix(out, lhs, " > ", rhs, mode),
865 BinOp::GtEq => emit_infix(out, lhs, " >= ", rhs, mode),
866 BinOp::And => emit_infix(out, lhs, " && ", rhs, mode),
867 BinOp::Or => emit_infix(out, lhs, " || ", rhs, mode),
868 BinOp::BitAnd => emit_infix(out, lhs, " & ", rhs, mode),
869 BinOp::BitOr => emit_infix(out, lhs, " | ", rhs, mode),
870 BinOp::BitXor => emit_infix(out, lhs, " ^ ", rhs, mode),
871 BinOp::Shl => emit_checked_shift(out, lhs, "checked_shl", rhs, "left-shift", mode),
872 BinOp::Shr => emit_checked_shift(out, lhs, "checked_shr", rhs, "right-shift", mode),
873 BinOp::Pow => emit_checked_pow(out, lhs, rhs, mode),
874 }
875}
876
877fn emit_bigint_floor_call(
881 out: &mut String,
882 method: &str,
883 lhs: &Expr,
884 rhs: &Expr,
885 mode: bool,
886) -> Result<(), RuchyCodegenError> {
887 write!(out, "xpile_bigint::{method}(&")?;
888 emit_expr(out, lhs, mode)?;
889 write!(out, ", &")?;
890 emit_expr(out, rhs, mode)?;
891 write!(out, ")")?;
892 Ok(())
893}
894
895fn emit_checked_pow(
896 out: &mut String,
897 lhs: &Expr,
898 rhs: &Expr,
899 mode: bool,
900) -> Result<(), RuchyCodegenError> {
901 write!(out, "(")?;
902 emit_expr(out, lhs, mode)?;
903 write!(out, ").checked_pow(u32::try_from(")?;
904 emit_expr(out, rhs, mode)?;
905 write!(
906 out,
907 ").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\")"
908 )?;
909 Ok(())
910}
911
912fn emit_checked_shift(
913 out: &mut String,
914 lhs: &Expr,
915 method: &str,
916 rhs: &Expr,
917 op_name: &str,
918 mode: bool,
919) -> Result<(), RuchyCodegenError> {
920 write!(out, "(")?;
921 emit_expr(out, lhs, mode)?;
922 write!(out, ").{method}(u32::try_from(")?;
923 emit_expr(out, rhs, mode)?;
924 write!(
925 out,
926 ").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\")"
927 )?;
928 Ok(())
929}
930
931fn emit_checked(
932 out: &mut String,
933 lhs: &Expr,
934 method: &str,
935 rhs: &Expr,
936 op_name: &str,
937 mode: bool,
938) -> Result<(), RuchyCodegenError> {
939 write!(out, "(")?;
940 emit_expr(out, lhs, mode)?;
941 write!(out, ").{method}(")?;
942 emit_expr(out, rhs, mode)?;
943 write!(
944 out,
945 ").expect(\"xpile: i64 {op_name} overflow; bigint promotion (contract C-PY-INT-ARITH slow path) not yet implemented\")"
946 )?;
947 Ok(())
948}
949
950fn emit_infix(
951 out: &mut String,
952 lhs: &Expr,
953 op: &str,
954 rhs: &Expr,
955 mode: bool,
956) -> Result<(), RuchyCodegenError> {
957 write!(out, "(")?;
958 emit_expr(out, lhs, mode)?;
959 out.push_str(op);
960 emit_expr(out, rhs, mode)?;
961 write!(out, ")")?;
962 Ok(())
963}
964
965pub struct RuchyBackend;
966
967impl Backend for RuchyBackend {
968 fn name(&self) -> &'static str {
969 "ruchy"
970 }
971
972 fn targets(&self) -> &[Target] {
973 &[Target::Ruchy]
974 }
975
976 fn lower(&self, module: &Module, _config: &BackendConfig) -> Result<Artifact, BackendError> {
977 let primary = emit_module(module).map_err(|e| BackendError::Lower(e.to_string()))?;
978 Ok(Artifact {
979 primary,
980 sidecars: Vec::new(),
981 citations: Vec::new(),
982 quorum_status: QuorumStatus::Single {
983 emitter: "xpile-ruchy-codegen".to_string(),
984 },
985 })
986 }
987}
988
989#[cfg(test)]
990mod tests {
991 use super::*;
992 use xpile_meta_hir::{Module, SourceLang};
993
994 fn module_with(name: &str, items: Vec<Item>) -> Module {
995 Module {
996 name: name.into(),
997 source_lang: SourceLang::Python,
998 items,
999 ffi_boundaries: Vec::new(),
1000 }
1001 }
1002
1003 fn add_fn() -> Function {
1004 Function {
1005 name: "add".into(),
1006 params: vec![
1007 Param {
1008 name: "a".into(),
1009 ty: Type::I64,
1010 mutable: false,
1011 },
1012 Param {
1013 name: "b".into(),
1014 ty: Type::I64,
1015 mutable: false,
1016 },
1017 ],
1018 return_type: Type::I64,
1019 body: Block {
1020 stmts: vec![],
1021 trailing_return: Expr::BinOp {
1022 op: BinOp::Add,
1023 lhs: Box::new(Expr::Ident("a".into())),
1024 rhs: Box::new(Expr::Ident("b".into())),
1025 },
1026 },
1027 }
1028 }
1029
1030 #[test]
1031 fn emits_fun_keyword_not_pub_fn() {
1032 let m = module_with("fixture", vec![Item::Function(add_fn())]);
1033 let ruchy = emit_module(&m).expect("emit ok");
1034 assert!(
1035 ruchy.contains("fun add("),
1036 "Ruchy uses `fun`, not `fn` or `pub fn`: got\n{}",
1037 ruchy
1038 );
1039 assert!(
1040 !ruchy.contains("pub fn"),
1041 "Ruchy emission must not produce `pub fn` (that's Rust)"
1042 );
1043 assert!(
1047 ruchy.contains("checked_add"),
1048 "expected checked_add: {ruchy}"
1049 );
1050 assert!(ruchy.contains("C-PY-INT-ARITH"));
1051 }
1052
1053 #[test]
1054 fn ruchy_floordiv_also_uses_div_euclid() {
1055 let f = Function {
1056 name: "fdiv".into(),
1057 params: vec![
1058 Param {
1059 name: "a".into(),
1060 ty: Type::I64,
1061 mutable: false,
1062 },
1063 Param {
1064 name: "b".into(),
1065 ty: Type::I64,
1066 mutable: false,
1067 },
1068 ],
1069 return_type: Type::I64,
1070 body: Block {
1071 stmts: vec![],
1072 trailing_return: Expr::BinOp {
1073 op: BinOp::FloorDiv,
1074 lhs: Box::new(Expr::Ident("a".into())),
1075 rhs: Box::new(Expr::Ident("b".into())),
1076 },
1077 },
1078 };
1079 let m = module_with("fixture", vec![Item::Function(f)]);
1080 let ruchy = emit_module(&m).expect("emit ok");
1081 assert!(ruchy.contains("div_euclid"));
1082 assert!(!ruchy.contains(" / "));
1083 }
1084}