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, Param, Stmt, Type, UnOp,
17};
18
19fn float_op_sym(op: FloatOp) -> &'static str {
21 match op {
22 FloatOp::Add => "+",
23 FloatOp::Sub => "-",
24 FloatOp::Mul => "*",
25 FloatOp::Div => "/",
26 }
27}
28
29#[derive(Debug, thiserror::Error)]
30pub enum RuchyCodegenError {
31 #[error("unsupported item: {0}")]
32 Unsupported(String),
33 #[error("formatting error: {0}")]
34 Format(#[from] std::fmt::Error),
35}
36
37pub fn emit_module(module: &Module) -> Result<String, RuchyCodegenError> {
38 let mut out = String::new();
39 writeln!(
40 out,
41 "// xpile-generated from {:?} module {}",
42 module.source_lang, module.name
43 )?;
44 writeln!(out)?;
45 for item in &module.items {
46 match item {
47 Item::Function(f) => emit_function(&mut out, f)?,
48 }
49 }
50 Ok(out)
51}
52
53fn emit_function(out: &mut String, f: &Function) -> Result<(), RuchyCodegenError> {
54 emit_contract_citations(out, f)?;
55 write!(out, "fun {}(", f.name)?;
57 for (i, p) in f.params.iter().enumerate() {
58 if i > 0 {
59 write!(out, ", ")?;
60 }
61 emit_param(out, p)?;
62 }
63 write!(out, ") -> ")?;
64 emit_type(out, &f.return_type)?;
65 writeln!(out, " {{")?;
66 let mode = function_bigint_mode(f);
67 emit_block(out, &f.body, mode)?;
68 writeln!(out, "}}")?;
69 Ok(())
70}
71
72fn function_bigint_mode(f: &Function) -> bool {
79 if matches!(f.return_type, Type::BigInt) {
80 return true;
81 }
82 if f.params.iter().any(|p| matches!(p.ty, Type::BigInt)) {
83 return true;
84 }
85 fn stmt_has_bigint(s: &Stmt) -> bool {
86 match s {
87 Stmt::Let { ty, .. } => matches!(ty, Type::BigInt),
88 Stmt::Assign { .. } | Stmt::Assert { .. } | Stmt::Return(_) => false,
90 Stmt::While { body, .. } | Stmt::ForEach { body, .. } => {
91 body.iter().any(stmt_has_bigint)
92 }
93 Stmt::If {
95 then_body,
96 else_body,
97 ..
98 } => then_body.iter().any(stmt_has_bigint) || else_body.iter().any(stmt_has_bigint),
99 Stmt::ListAppend { .. } => false,
101 Stmt::IndexAssign { .. } => false,
103 Stmt::DictSet { .. } => false,
105 Stmt::Cmd { .. } => false,
108 Stmt::Pipeline { .. } => false,
110 Stmt::ShellLoop { .. } => false,
112 Stmt::ShellAssign { .. } => false,
114 }
115 }
116 f.body.stmts.iter().any(stmt_has_bigint)
117}
118
119fn emit_contract_citations(out: &mut String, f: &Function) -> Result<(), RuchyCodegenError> {
122 for id in f.applicable_contracts() {
123 writeln!(out, "// xpile-contract: {id}")?;
124 }
125 Ok(())
126}
127
128fn emit_block(out: &mut String, block: &Block, mode: bool) -> Result<(), RuchyCodegenError> {
129 for stmt in &block.stmts {
130 emit_stmt(out, stmt, mode)?;
131 }
132 write!(out, " ")?;
133 emit_expr(out, &block.trailing_return, mode)?;
134 writeln!(out)?;
135 Ok(())
136}
137
138fn emit_stmt(out: &mut String, stmt: &Stmt, mode: bool) -> Result<(), RuchyCodegenError> {
139 emit_stmt_indented(out, stmt, " ", mode)
140}
141
142fn emit_stmt_indented(
143 out: &mut String,
144 stmt: &Stmt,
145 indent: &str,
146 mode: bool,
147) -> Result<(), RuchyCodegenError> {
148 match stmt {
149 Stmt::Let {
150 name,
151 ty,
152 value,
153 mutable,
154 } => {
155 let kw = if *mutable { "let mut" } else { "let" };
156 write!(out, "{indent}{kw} {name}: ")?;
157 emit_type(out, ty)?;
158 write!(out, " = ")?;
159 emit_expr(out, value, mode)?;
160 writeln!(out, ";")?;
161 Ok(())
162 }
163 Stmt::Return(e) => {
165 write!(out, "{indent}return ")?;
166 emit_expr(out, e, mode)?;
167 writeln!(out, ";")?;
168 Ok(())
169 }
170 Stmt::If {
172 cond,
173 then_body,
174 else_body,
175 } => {
176 write!(out, "{indent}if ")?;
177 emit_expr(out, cond, mode)?;
178 writeln!(out, " {{")?;
179 let inner = format!("{indent} ");
180 for s in then_body {
181 emit_stmt_indented(out, s, &inner, mode)?;
182 }
183 if else_body.is_empty() {
184 writeln!(out, "{indent}}}")?;
185 } else {
186 writeln!(out, "{indent}}} else {{")?;
187 for s in else_body {
188 emit_stmt_indented(out, s, &inner, mode)?;
189 }
190 writeln!(out, "{indent}}}")?;
191 }
192 Ok(())
193 }
194 Stmt::Assign { name, value } => {
195 write!(out, "{indent}{name} = ")?;
196 emit_expr(out, value, mode)?;
197 writeln!(out, ";")?;
198 Ok(())
199 }
200 Stmt::While { cond, body } => {
201 write!(out, "{indent}while ")?;
202 emit_expr(out, cond, mode)?;
203 writeln!(out, " {{")?;
204 let inner = format!("{indent} ");
205 for s in body {
206 emit_stmt_indented(out, s, &inner, mode)?;
207 }
208 writeln!(out, "{indent}}}")?;
209 Ok(())
210 }
211 Stmt::ForEach {
214 var,
215 iter,
216 body,
217 over_keys,
218 ..
219 } => {
220 let method = if *over_keys { "keys" } else { "iter" };
222 write!(out, "{indent}for {var} in ")?;
223 emit_expr(out, iter, mode)?;
224 writeln!(out, ".{method}().cloned() {{")?;
225 let inner = format!("{indent} ");
226 for s in body {
227 emit_stmt_indented(out, s, &inner, mode)?;
228 }
229 writeln!(out, "{indent}}}")?;
230 Ok(())
231 }
232 Stmt::ListAppend { list_name, elem } => {
234 write!(out, "{indent}{list_name}.push(")?;
235 emit_expr(out, elem, mode)?;
236 writeln!(out, ");")?;
237 Ok(())
238 }
239 Stmt::IndexAssign {
242 list_name,
243 index,
244 value,
245 } => {
246 write!(out, "{indent}{list_name}[")?;
247 emit_expr(out, index, mode)?;
248 out.push_str(" as usize] = ");
249 emit_expr(out, value, mode)?;
250 writeln!(out, ";")?;
251 Ok(())
252 }
253 Stmt::DictSet {
259 dict_name,
260 key,
261 value,
262 } => {
263 write!(out, "{indent}{{ let __xpile_dict_val = ")?;
264 emit_expr(out, value, mode)?;
265 write!(out, "; {dict_name}.insert(")?;
266 emit_expr(out, key, mode)?;
267 writeln!(out, ".clone(), __xpile_dict_val); }}")?;
268 Ok(())
269 }
270 Stmt::Assert { cond } => {
271 write!(out, "{indent}assert!(")?;
272 emit_expr(out, cond, mode)?;
273 writeln!(out, ");")?;
274 Ok(())
275 }
276 Stmt::Cmd { program, args } => Err(RuchyCodegenError::Unsupported(format!(
281 "Ruchy backend does not lower Stmt::Cmd (`{program}` with {} arg(s)) — \
282 contract C-BASHRS-POSIX-IDEMPOTENCE governs this construct; \
283 use `--target shell` to emit POSIX sh via bashrs-backend",
284 args.len()
285 ))),
286 Stmt::Pipeline { stages } => Err(RuchyCodegenError::Unsupported(format!(
288 "Ruchy backend does not lower Stmt::Pipeline ({} stages) — \
289 contract C-BASHRS-POSIX-IDEMPOTENCE governs shell pipelines; \
290 use `--target shell`",
291 stages.len()
292 ))),
293 Stmt::ShellLoop { .. } => Err(RuchyCodegenError::Unsupported(
295 "Ruchy backend does not lower Stmt::ShellLoop — \
296 contract C-BASHRS-POSIX-IDEMPOTENCE governs shell loops; \
297 use `--target shell`"
298 .into(),
299 )),
300 Stmt::ShellAssign { name, .. } => Err(RuchyCodegenError::Unsupported(format!(
302 "Ruchy backend does not lower Stmt::ShellAssign (`{name}=…`) — \
303 contract C-BASHRS-POSIX-IDEMPOTENCE governs shell variable assignment; \
304 use `--target shell`"
305 ))),
306 }
307}
308
309fn emit_param(out: &mut String, p: &Param) -> Result<(), RuchyCodegenError> {
310 if p.mutable {
312 write!(out, "mut ")?;
313 }
314 write!(out, "{}: ", p.name)?;
315 emit_type(out, &p.ty)?;
316 Ok(())
317}
318
319fn escape_ruchy_str(s: &str) -> String {
322 let mut out = String::with_capacity(s.len());
323 for c in s.chars() {
324 match c {
325 '\\' => out.push_str("\\\\"),
326 '"' => out.push_str("\\\""),
327 other => out.push(other),
328 }
329 }
330 out
331}
332
333fn emit_type(out: &mut String, t: &Type) -> Result<(), RuchyCodegenError> {
334 match t {
335 Type::I64 => out.push_str("i64"),
336 Type::F64 => out.push_str("f64"),
338 Type::Bool => out.push_str("bool"),
339 Type::BigInt => out.push_str("xpile_bigint::BigInt"),
341 Type::Str => out.push_str("String"),
344 Type::List(elem_ty) => {
346 out.push_str("Vec<");
347 emit_type(out, elem_ty)?;
348 out.push('>');
349 }
350 Type::Dict(k_ty, v_ty) => {
352 out.push_str("std::collections::HashMap<");
353 emit_type(out, k_ty)?;
354 out.push_str(", ");
355 emit_type(out, v_ty)?;
356 out.push('>');
357 }
358 Type::ShellString | Type::ExitCode => {
360 return Err(RuchyCodegenError::Unsupported(format!(
361 "Ruchy backend does not lower {t:?} — \
362 contract C-BASHRS-POSIX-IDEMPOTENCE governs the bashrs type domain; \
363 use `--target shell`"
364 )));
365 }
366 }
367 Ok(())
368}
369
370fn emit_expr(out: &mut String, e: &Expr, mode: bool) -> Result<(), RuchyCodegenError> {
371 match e {
372 Expr::Ident(name) => {
373 if mode {
379 write!(out, "{}.clone()", name)?;
380 } else {
381 write!(out, "{}", name)?;
382 }
383 }
384 Expr::LitInt(v) => {
385 if mode {
386 write!(out, "xpile_bigint::BigInt::from({}i64)", v)?;
387 } else {
388 write!(out, "{}i64", v)?;
389 }
390 }
391 Expr::LitFloat(v) => write!(out, "{}f64", v)?,
393 Expr::FloatBinOp { op, lhs, rhs } => {
394 out.push('(');
395 emit_expr(out, lhs, mode)?;
396 write!(out, " {} ", float_op_sym(*op))?;
397 emit_expr(out, rhs, mode)?;
398 out.push(')');
399 }
400 Expr::LitBool(b) => write!(out, "{}", b)?,
403 Expr::BinOp { op, lhs, rhs } => emit_binop(out, *op, lhs, rhs, mode)?,
404 Expr::Concat { lhs, rhs } => {
408 out.push_str("format!(\"{}{}\", ");
409 emit_expr(out, lhs, mode)?;
410 out.push_str(", ");
411 emit_expr(out, rhs, mode)?;
412 out.push(')');
413 }
414 Expr::ListLit(elems) => {
416 out.push_str("vec![");
417 for (i, e) in elems.iter().enumerate() {
418 if i > 0 {
419 out.push_str(", ");
420 }
421 emit_expr(out, e, mode)?;
422 }
423 out.push(']');
424 }
425 Expr::DictLit(pairs) => {
429 if pairs.is_empty() {
430 out.push_str("std::collections::HashMap::new()");
431 } else {
432 out.push_str("{ let mut m = std::collections::HashMap::new(); ");
433 for (k, v) in pairs {
434 out.push_str("m.insert(");
435 emit_expr(out, k, mode)?;
436 out.push_str(", ");
437 emit_expr(out, v, mode)?;
438 out.push_str("); ");
439 }
440 out.push_str("m }");
441 }
442 }
443 Expr::Index { collection, index } => {
446 emit_expr(out, collection, mode)?;
447 out.push('[');
448 emit_expr(out, index, mode)?;
449 out.push_str(" as usize].clone()");
450 }
451 Expr::DictGet { dict, key } => {
454 emit_expr(out, dict, mode)?;
455 out.push_str("[&(");
456 emit_expr(out, key, mode)?;
457 out.push_str(")].clone()");
458 }
459 Expr::DictGetOr { dict, key, default } => {
460 emit_expr(out, dict, mode)?;
461 out.push_str(".get(&(");
462 emit_expr(out, key, mode)?;
463 out.push_str(")).cloned().unwrap_or(");
464 emit_expr(out, default, mode)?;
465 out.push(')');
466 }
467 Expr::DictContains { dict, key } => {
468 emit_expr(out, dict, mode)?;
469 out.push_str(".contains_key(&(");
470 emit_expr(out, key, mode)?;
471 out.push_str("))");
472 }
473 Expr::Len(inner) => {
475 emit_expr(out, inner, mode)?;
476 out.push_str(".len() as i64");
477 }
478 Expr::IfExpr {
479 cond,
480 then_expr,
481 else_expr,
482 } => emit_if_expr(out, cond, then_expr, else_expr, mode)?,
483 Expr::Call { callee, args } => emit_call(out, callee, args, mode)?,
484 Expr::UnOp { op, operand } => emit_unop(out, *op, operand, mode)?,
485 Expr::LitStr(s) => {
489 write!(out, "String::from(\"{}\")", escape_ruchy_str(s))?;
490 }
491 Expr::QuotedString { .. } => {
494 return Err(RuchyCodegenError::Unsupported(
495 "Ruchy backend does not lower Expr::QuotedString — \
496 contract C-BASHRS-POSIX-IDEMPOTENCE governs quoted shell strings; \
497 use `--target shell`"
498 .into(),
499 ));
500 }
501 Expr::ShellVar(name) => {
503 return Err(RuchyCodegenError::Unsupported(format!(
504 "Ruchy backend does not lower Expr::ShellVar (${name}) — \
505 contract C-BASHRS-POSIX-IDEMPOTENCE governs shell variable refs; \
506 use `--target shell`"
507 )));
508 }
509 Expr::CommandSubstitution(_) => {
511 return Err(RuchyCodegenError::Unsupported(
512 "Ruchy backend does not lower Expr::CommandSubstitution — \
513 contract C-BASHRS-POSIX-IDEMPOTENCE governs shell substitution; \
514 use `--target shell`"
515 .into(),
516 ));
517 }
518 Expr::ShellSpecial(name) => {
520 return Err(RuchyCodegenError::Unsupported(format!(
521 "Ruchy backend does not lower Expr::ShellSpecial (${name}) — \
522 contract C-BASHRS-POSIX-IDEMPOTENCE governs shell special params; \
523 use `--target shell`"
524 )));
525 }
526 }
527 Ok(())
528}
529
530fn emit_unop(
531 out: &mut String,
532 op: UnOp,
533 operand: &Expr,
534 mode: bool,
535) -> Result<(), RuchyCodegenError> {
536 match op {
537 UnOp::Neg => {
538 if mode {
539 write!(out, "(-")?;
541 emit_expr(out, operand, mode)?;
542 write!(out, ")")?;
543 } else {
544 write!(out, "(")?;
548 emit_expr(out, operand, mode)?;
549 write!(
550 out,
551 ").checked_neg().expect(\"xpile: i64 negation overflow; bigint promotion (contract C-PY-INT-ARITH slow path) not yet implemented\")"
552 )?;
553 }
554 }
555 UnOp::Not => {
556 write!(out, "(!")?;
557 emit_expr(out, operand, mode)?;
558 write!(out, ")")?;
559 }
560 }
561 Ok(())
562}
563
564fn emit_call(
565 out: &mut String,
566 callee: &str,
567 args: &[Expr],
568 mode: bool,
569) -> Result<(), RuchyCodegenError> {
570 write!(out, "{}(", callee)?;
571 for (i, a) in args.iter().enumerate() {
572 if i > 0 {
573 write!(out, ", ")?;
574 }
575 emit_expr(out, a, mode)?;
576 }
577 write!(out, ")")?;
578 Ok(())
579}
580
581fn emit_if_expr(
584 out: &mut String,
585 cond: &Expr,
586 then_expr: &Expr,
587 else_expr: &Expr,
588 mode: bool,
589) -> Result<(), RuchyCodegenError> {
590 write!(out, "if ")?;
591 emit_expr(out, cond, mode)?;
592 write!(out, " {{ ")?;
593 emit_expr(out, then_expr, mode)?;
594 write!(out, " }} else ")?;
595 match else_expr {
596 Expr::IfExpr {
597 cond: c2,
598 then_expr: t2,
599 else_expr: e2,
600 } => emit_if_expr(out, c2, t2, e2, mode),
601 _ => {
602 write!(out, "{{ ")?;
603 emit_expr(out, else_expr, mode)?;
604 write!(out, " }}")?;
605 Ok(())
606 }
607 }
608}
609
610fn emit_binop(
622 out: &mut String,
623 op: BinOp,
624 lhs: &Expr,
625 rhs: &Expr,
626 mode: bool,
627) -> Result<(), RuchyCodegenError> {
628 match op {
629 BinOp::Add if mode => emit_infix(out, lhs, " + ", rhs, mode),
630 BinOp::Sub if mode => emit_infix(out, lhs, " - ", rhs, mode),
631 BinOp::Mul if mode => emit_infix(out, lhs, " * ", rhs, mode),
632 BinOp::FloorDiv if mode => emit_bigint_floor_call(out, "div_floor", lhs, rhs, mode),
633 BinOp::Mod if mode => emit_bigint_floor_call(out, "mod_floor", lhs, rhs, mode),
634 BinOp::BitAnd if mode => emit_infix(out, lhs, " & ", rhs, mode),
637 BinOp::BitOr if mode => emit_infix(out, lhs, " | ", rhs, mode),
638 BinOp::BitXor if mode => emit_infix(out, lhs, " ^ ", rhs, mode),
639 BinOp::Shl if mode => emit_bigint_floor_call(out, "shl", lhs, rhs, mode),
640 BinOp::Shr if mode => emit_bigint_floor_call(out, "shr", lhs, rhs, mode),
641 BinOp::Pow if mode => emit_bigint_floor_call(out, "pow", lhs, rhs, mode),
642 BinOp::Add => emit_checked(out, lhs, "checked_add", rhs, "addition", mode),
643 BinOp::Sub => emit_checked(out, lhs, "checked_sub", rhs, "subtraction", mode),
644 BinOp::Mul => emit_checked(out, lhs, "checked_mul", rhs, "multiplication", mode),
645 BinOp::FloorDiv => emit_checked(out, lhs, "checked_div_euclid", rhs, "floor-div", mode),
646 BinOp::Mod => emit_checked(out, lhs, "checked_rem_euclid", rhs, "modulo", mode),
647 BinOp::Eq => emit_infix(out, lhs, " == ", rhs, mode),
648 BinOp::NotEq => emit_infix(out, lhs, " != ", rhs, mode),
649 BinOp::Lt => emit_infix(out, lhs, " < ", rhs, mode),
650 BinOp::LtEq => emit_infix(out, lhs, " <= ", rhs, mode),
651 BinOp::Gt => emit_infix(out, lhs, " > ", rhs, mode),
652 BinOp::GtEq => emit_infix(out, lhs, " >= ", rhs, mode),
653 BinOp::And => emit_infix(out, lhs, " && ", rhs, mode),
654 BinOp::Or => emit_infix(out, lhs, " || ", rhs, mode),
655 BinOp::BitAnd => emit_infix(out, lhs, " & ", rhs, mode),
656 BinOp::BitOr => emit_infix(out, lhs, " | ", rhs, mode),
657 BinOp::BitXor => emit_infix(out, lhs, " ^ ", rhs, mode),
658 BinOp::Shl => emit_checked_shift(out, lhs, "checked_shl", rhs, "left-shift", mode),
659 BinOp::Shr => emit_checked_shift(out, lhs, "checked_shr", rhs, "right-shift", mode),
660 BinOp::Pow => emit_checked_pow(out, lhs, rhs, mode),
661 }
662}
663
664fn emit_bigint_floor_call(
668 out: &mut String,
669 method: &str,
670 lhs: &Expr,
671 rhs: &Expr,
672 mode: bool,
673) -> Result<(), RuchyCodegenError> {
674 write!(out, "xpile_bigint::{method}(&")?;
675 emit_expr(out, lhs, mode)?;
676 write!(out, ", &")?;
677 emit_expr(out, rhs, mode)?;
678 write!(out, ")")?;
679 Ok(())
680}
681
682fn emit_checked_pow(
683 out: &mut String,
684 lhs: &Expr,
685 rhs: &Expr,
686 mode: bool,
687) -> Result<(), RuchyCodegenError> {
688 write!(out, "(")?;
689 emit_expr(out, lhs, mode)?;
690 write!(out, ").checked_pow(u32::try_from(")?;
691 emit_expr(out, rhs, mode)?;
692 write!(
693 out,
694 ").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\")"
695 )?;
696 Ok(())
697}
698
699fn emit_checked_shift(
700 out: &mut String,
701 lhs: &Expr,
702 method: &str,
703 rhs: &Expr,
704 op_name: &str,
705 mode: bool,
706) -> Result<(), RuchyCodegenError> {
707 write!(out, "(")?;
708 emit_expr(out, lhs, mode)?;
709 write!(out, ").{method}(u32::try_from(")?;
710 emit_expr(out, rhs, mode)?;
711 write!(
712 out,
713 ").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\")"
714 )?;
715 Ok(())
716}
717
718fn emit_checked(
719 out: &mut String,
720 lhs: &Expr,
721 method: &str,
722 rhs: &Expr,
723 op_name: &str,
724 mode: bool,
725) -> Result<(), RuchyCodegenError> {
726 write!(out, "(")?;
727 emit_expr(out, lhs, mode)?;
728 write!(out, ").{method}(")?;
729 emit_expr(out, rhs, mode)?;
730 write!(
731 out,
732 ").expect(\"xpile: i64 {op_name} overflow; bigint promotion (contract C-PY-INT-ARITH slow path) not yet implemented\")"
733 )?;
734 Ok(())
735}
736
737fn emit_infix(
738 out: &mut String,
739 lhs: &Expr,
740 op: &str,
741 rhs: &Expr,
742 mode: bool,
743) -> Result<(), RuchyCodegenError> {
744 write!(out, "(")?;
745 emit_expr(out, lhs, mode)?;
746 out.push_str(op);
747 emit_expr(out, rhs, mode)?;
748 write!(out, ")")?;
749 Ok(())
750}
751
752pub struct RuchyBackend;
753
754impl Backend for RuchyBackend {
755 fn name(&self) -> &'static str {
756 "ruchy"
757 }
758
759 fn targets(&self) -> &[Target] {
760 &[Target::Ruchy]
761 }
762
763 fn lower(&self, module: &Module, _config: &BackendConfig) -> Result<Artifact, BackendError> {
764 let primary = emit_module(module).map_err(|e| BackendError::Lower(e.to_string()))?;
765 Ok(Artifact {
766 primary,
767 sidecars: Vec::new(),
768 citations: Vec::new(),
769 quorum_status: QuorumStatus::Single {
770 emitter: "xpile-ruchy-codegen".to_string(),
771 },
772 })
773 }
774}
775
776#[cfg(test)]
777mod tests {
778 use super::*;
779 use xpile_meta_hir::{Module, SourceLang};
780
781 fn module_with(name: &str, items: Vec<Item>) -> Module {
782 Module {
783 name: name.into(),
784 source_lang: SourceLang::Python,
785 items,
786 ffi_boundaries: Vec::new(),
787 }
788 }
789
790 fn add_fn() -> Function {
791 Function {
792 name: "add".into(),
793 params: vec![
794 Param {
795 name: "a".into(),
796 ty: Type::I64,
797 mutable: false,
798 },
799 Param {
800 name: "b".into(),
801 ty: Type::I64,
802 mutable: false,
803 },
804 ],
805 return_type: Type::I64,
806 body: Block {
807 stmts: vec![],
808 trailing_return: Expr::BinOp {
809 op: BinOp::Add,
810 lhs: Box::new(Expr::Ident("a".into())),
811 rhs: Box::new(Expr::Ident("b".into())),
812 },
813 },
814 }
815 }
816
817 #[test]
818 fn emits_fun_keyword_not_pub_fn() {
819 let m = module_with("fixture", vec![Item::Function(add_fn())]);
820 let ruchy = emit_module(&m).expect("emit ok");
821 assert!(
822 ruchy.contains("fun add("),
823 "Ruchy uses `fun`, not `fn` or `pub fn`: got\n{}",
824 ruchy
825 );
826 assert!(
827 !ruchy.contains("pub fn"),
828 "Ruchy emission must not produce `pub fn` (that's Rust)"
829 );
830 assert!(
834 ruchy.contains("checked_add"),
835 "expected checked_add: {ruchy}"
836 );
837 assert!(ruchy.contains("C-PY-INT-ARITH"));
838 }
839
840 #[test]
841 fn ruchy_floordiv_also_uses_div_euclid() {
842 let f = Function {
843 name: "fdiv".into(),
844 params: vec![
845 Param {
846 name: "a".into(),
847 ty: Type::I64,
848 mutable: false,
849 },
850 Param {
851 name: "b".into(),
852 ty: Type::I64,
853 mutable: false,
854 },
855 ],
856 return_type: Type::I64,
857 body: Block {
858 stmts: vec![],
859 trailing_return: Expr::BinOp {
860 op: BinOp::FloorDiv,
861 lhs: Box::new(Expr::Ident("a".into())),
862 rhs: Box::new(Expr::Ident("b".into())),
863 },
864 },
865 };
866 let m = module_with("fixture", vec![Item::Function(f)]);
867 let ruchy = emit_module(&m).expect("emit ok");
868 assert!(ruchy.contains("div_euclid"));
869 assert!(!ruchy.contains(" / "));
870 }
871}