1#![doc = include_str!("../README.md")]
2
3use num_bigint::BigInt;
9use rhai::{def_package, plugin::*};
10
11#[cold]
13#[inline(never)]
14fn cross_type_cmp_err(
15 lhs: &'static str,
16 rhs: &'static str,
17 advice: &'static str,
18) -> Box<rhai::EvalAltResult> {
19 format!("Cannot compare {lhs} with {rhs}; {advice}").into()
20}
21
22macro_rules! bigint_cross_type_cmp_module {
25 ($mod_name:ident, $t:ty, $type_name:literal, $advice:literal) => {
26 #[export_module]
27 pub(crate) mod $mod_name {
28 use num_bigint::BigInt;
29 use rhai::plugin::*;
30
31 #[rhai_fn(name = "==", pure, return_raw)]
32 pub fn eq_bigint(_l: &mut BigInt, _r: $t) -> Result<bool, Box<rhai::EvalAltResult>> {
33 Err(crate::cross_type_cmp_err("BigInt", $type_name, $advice))
34 }
35
36 #[rhai_fn(name = "!=", pure, return_raw)]
37 pub fn ne_bigint(_l: &mut BigInt, _r: $t) -> Result<bool, Box<rhai::EvalAltResult>> {
38 Err(crate::cross_type_cmp_err("BigInt", $type_name, $advice))
39 }
40
41 #[rhai_fn(name = "==", pure, return_raw)]
42 pub fn eq_type(_l: &mut $t, _r: BigInt) -> Result<bool, Box<rhai::EvalAltResult>> {
43 Err(crate::cross_type_cmp_err($type_name, "BigInt", $advice))
44 }
45
46 #[rhai_fn(name = "!=", pure, return_raw)]
47 pub fn ne_type(_l: &mut $t, _r: BigInt) -> Result<bool, Box<rhai::EvalAltResult>> {
48 Err(crate::cross_type_cmp_err($type_name, "BigInt", $advice))
49 }
50
51 #[rhai_fn(name = "<", pure, return_raw)]
52 pub fn lt_bigint(_l: &mut BigInt, _r: $t) -> Result<bool, Box<rhai::EvalAltResult>> {
53 Err(crate::cross_type_cmp_err("BigInt", $type_name, $advice))
54 }
55
56 #[rhai_fn(name = "<=", pure, return_raw)]
57 pub fn le_bigint(_l: &mut BigInt, _r: $t) -> Result<bool, Box<rhai::EvalAltResult>> {
58 Err(crate::cross_type_cmp_err("BigInt", $type_name, $advice))
59 }
60
61 #[rhai_fn(name = ">", pure, return_raw)]
62 pub fn gt_bigint(_l: &mut BigInt, _r: $t) -> Result<bool, Box<rhai::EvalAltResult>> {
63 Err(crate::cross_type_cmp_err("BigInt", $type_name, $advice))
64 }
65
66 #[rhai_fn(name = ">=", pure, return_raw)]
67 pub fn ge_bigint(_l: &mut BigInt, _r: $t) -> Result<bool, Box<rhai::EvalAltResult>> {
68 Err(crate::cross_type_cmp_err("BigInt", $type_name, $advice))
69 }
70
71 #[rhai_fn(name = "<", pure, return_raw)]
72 pub fn lt_type(_l: &mut $t, _r: BigInt) -> Result<bool, Box<rhai::EvalAltResult>> {
73 Err(crate::cross_type_cmp_err($type_name, "BigInt", $advice))
74 }
75
76 #[rhai_fn(name = "<=", pure, return_raw)]
77 pub fn le_type(_l: &mut $t, _r: BigInt) -> Result<bool, Box<rhai::EvalAltResult>> {
78 Err(crate::cross_type_cmp_err($type_name, "BigInt", $advice))
79 }
80
81 #[rhai_fn(name = ">", pure, return_raw)]
82 pub fn gt_type(_l: &mut $t, _r: BigInt) -> Result<bool, Box<rhai::EvalAltResult>> {
83 Err(crate::cross_type_cmp_err($type_name, "BigInt", $advice))
84 }
85
86 #[rhai_fn(name = ">=", pure, return_raw)]
87 pub fn ge_type(_l: &mut $t, _r: BigInt) -> Result<bool, Box<rhai::EvalAltResult>> {
88 Err(crate::cross_type_cmp_err($type_name, "BigInt", $advice))
89 }
90 }
91 };
92}
93
94#[export_module]
95mod bigint_functions {
96 use num_bigint::{BigInt, Sign};
97 use num_traits::{FromPrimitive, ToPrimitive, Zero};
98 use rhai::INT;
99
100 pub fn parse_bigint(value: INT) -> BigInt {
102 value.into()
103 }
104
105 #[rhai_fn(name = "parse_bigint", return_raw)]
107 pub fn parse_bigint_from_float(value: rhai::FLOAT) -> Result<BigInt, Box<rhai::EvalAltResult>> {
108 BigInt::from_f64(value)
109 .ok_or_else(|| format!("Cannot convert {value} to BigInt: value must be finite").into())
110 }
111
112 #[rhai_fn(name = "parse_bigint", return_raw)]
114 pub fn parse_bigint_from_str(value: String) -> Result<BigInt, Box<rhai::EvalAltResult>> {
115 value
116 .parse::<BigInt>()
117 .map_err(|e| format!("Cannot parse {value:?} as BigInt: {e}").into())
118 }
119
120 #[rhai_fn(name = "to_bigint", pure)]
122 pub fn int_to_bigint(value: &mut INT) -> BigInt {
123 BigInt::from(*value)
124 }
125
126 #[rhai_fn(name = "to_bigint", return_raw, pure)]
129 pub fn float_to_bigint(value: &mut rhai::FLOAT) -> Result<BigInt, Box<rhai::EvalAltResult>> {
130 BigInt::from_f64(*value)
131 .ok_or_else(|| format!("Cannot convert {value} to BigInt: value must be finite").into())
132 }
133
134 #[rhai_fn(name = "+", pure)]
135 pub fn add(l: &mut BigInt, r: BigInt) -> BigInt {
136 l.clone() + r
137 }
138
139 #[rhai_fn(name = "-", pure)]
140 pub fn sub(l: &mut BigInt, r: BigInt) -> BigInt {
141 l.clone() - r
142 }
143
144 #[rhai_fn(name = "*", pure)]
145 pub fn mul(l: &mut BigInt, r: BigInt) -> BigInt {
146 l.clone() * r
147 }
148
149 #[rhai_fn(name = "/", pure, return_raw)]
150 pub fn div(l: &mut BigInt, r: BigInt) -> Result<BigInt, Box<rhai::EvalAltResult>> {
151 if r.is_zero() {
152 return Err("Division by zero".into());
153 }
154 Ok(l.clone() / r)
155 }
156
157 #[rhai_fn(name = "%", pure, return_raw)]
158 pub fn rem(l: &mut BigInt, r: BigInt) -> Result<BigInt, Box<rhai::EvalAltResult>> {
159 if r.is_zero() {
160 return Err("Modulo by zero".into());
161 }
162 Ok(l.clone() % r)
163 }
164
165 #[rhai_fn(name = "-", pure)]
166 pub fn neg(value: &mut BigInt) -> BigInt {
167 -value.clone()
168 }
169
170 #[rhai_fn(name = "**", pure, return_raw)]
173 pub fn pow(base: &mut BigInt, exp: INT) -> Result<BigInt, Box<rhai::EvalAltResult>> {
174 if exp < 0 {
179 return Err(format!("Exponent must be non-negative, got {exp}").into());
180 }
181 let exp_u32 = u32::try_from(exp).map_err(|_| -> Box<rhai::EvalAltResult> {
182 format!("Exponent {exp} is too large, must be at most {}", u32::MAX).into()
183 })?;
184 Ok(base.clone().pow(exp_u32))
185 }
186
187 #[rhai_fn(name = "&", pure)]
188 pub fn bitand(l: &mut BigInt, r: BigInt) -> BigInt {
189 l.clone() & r
190 }
191
192 #[rhai_fn(name = "|", pure)]
193 pub fn bitor(l: &mut BigInt, r: BigInt) -> BigInt {
194 l.clone() | r
195 }
196
197 #[rhai_fn(name = "^", pure)]
198 pub fn bitxor(l: &mut BigInt, r: BigInt) -> BigInt {
199 l.clone() ^ r
200 }
201
202 fn validate_shift_amount(shift: INT) -> Result<u32, Box<rhai::EvalAltResult>> {
203 if shift < 0 {
207 return Err(format!("Shift amount must be non-negative, got {shift}").into());
208 }
209
210 u32::try_from(shift).map_err(|_| -> Box<rhai::EvalAltResult> {
211 format!("Shift amount is too large, must be at most {}", u32::MAX).into()
212 })
213 }
214
215 #[rhai_fn(name = "<<", pure, return_raw)]
217 pub fn shl(value: &mut BigInt, shift: INT) -> Result<BigInt, Box<rhai::EvalAltResult>> {
218 let shift_u32 = validate_shift_amount(shift)?;
219 Ok(value.clone() << shift_u32)
220 }
221
222 #[rhai_fn(name = ">>", pure, return_raw)]
224 pub fn shr(value: &mut BigInt, shift: INT) -> Result<BigInt, Box<rhai::EvalAltResult>> {
225 let shift_u32 = validate_shift_amount(shift)?;
226 Ok(value.clone() >> shift_u32)
227 }
228
229 #[rhai_fn(name = "==", pure)]
230 pub fn eq(l: &mut BigInt, r: BigInt) -> bool {
231 *l == r
232 }
233
234 #[rhai_fn(name = "!=", pure)]
235 pub fn ne(l: &mut BigInt, r: BigInt) -> bool {
236 *l != r
237 }
238
239 #[rhai_fn(name = "<", pure)]
240 pub fn lt(l: &mut BigInt, r: BigInt) -> bool {
241 *l < r
242 }
243
244 #[rhai_fn(name = "<=", pure)]
245 pub fn le(l: &mut BigInt, r: BigInt) -> bool {
246 *l <= r
247 }
248
249 #[rhai_fn(name = ">", pure)]
250 pub fn gt(l: &mut BigInt, r: BigInt) -> bool {
251 *l > r
252 }
253
254 #[rhai_fn(name = ">=", pure)]
255 pub fn ge(l: &mut BigInt, r: BigInt) -> bool {
256 *l >= r
257 }
258
259 #[rhai_fn(name = "to_string", pure)]
261 pub fn to_string(value: &mut BigInt) -> String {
262 value.to_string()
263 }
264
265 #[rhai_fn(name = "to_hex", pure)]
268 pub fn to_hex(value: &mut BigInt) -> String {
269 let hex = format!("{:x}", value.magnitude());
270 if value.sign() == Sign::Minus {
271 format!("-0x{hex}")
272 } else {
273 format!("0x{hex}")
274 }
275 }
276
277 #[rhai_fn(name = "to_float", pure, return_raw)]
280 pub fn to_float(value: &mut BigInt) -> Result<rhai::FLOAT, Box<rhai::EvalAltResult>> {
281 value
282 .to_f64()
283 .map(|f| f as rhai::FLOAT)
284 .filter(|f| f.is_finite())
285 .ok_or_else(|| {
286 "BigInt value is out of range for float (magnitude overflows to infinity)".into()
287 })
288 }
289}
290
291bigint_cross_type_cmp_module!(
292 bigint_int_cmp,
293 rhai::INT,
294 "int",
295 "wrap the int first: x == parse_bigint(42)"
296);
297
298bigint_cross_type_cmp_module!(
299 bigint_float_cmp,
300 rhai::FLOAT,
301 "float",
302 "convert the float to BigInt first via to_bigint() (truncates toward zero): x.to_bigint() == y"
303);
304
305bigint_cross_type_cmp_module!(
306 bigint_string_cmp,
307 rhai::ImmutableString,
308 "string",
309 r#"parse both sides first: parse_bigint(x) == parse_bigint(y)"#
310);
311
312bigint_cross_type_cmp_module!(
313 bigint_bool_cmp,
314 bool,
315 "bool",
316 "convert the BigInt to int first if a boolean check is needed: x != parse_bigint(0)"
317);
318
319def_package! {
320 pub BigIntPackage(lib) {
323 lib.set_custom_type::<BigInt>("BigInt");
324 combine_with_exported_module!(lib, "bigint", bigint_functions);
325 combine_with_exported_module!(lib, "bigint_int_cmp", bigint_int_cmp);
326 combine_with_exported_module!(lib, "bigint_float_cmp", bigint_float_cmp);
327 combine_with_exported_module!(lib, "bigint_string_cmp", bigint_string_cmp);
328 combine_with_exported_module!(lib, "bigint_bool_cmp", bigint_bool_cmp);
329 }
330}
331
332#[cfg(test)]
333mod tests {
334 use rhai::{packages::Package, Engine};
335
336 use super::*;
337
338 #[track_caller]
342 fn assert_cmp_error(engine: &Engine, script: &str, expected_fragment: &str) {
343 match engine.eval::<bool>(script) {
344 Ok(v) => panic!("expected error for `{script}`, got Ok({v})"),
345 Err(e) => assert!(
346 e.to_string().contains(expected_fragment),
347 "script `{script}`\n expected fragment: {expected_fragment:?}\n actual error: {e}"
348 ),
349 }
350 }
351
352 #[test]
353 fn test_rhai_integration() {
354 let mut engine = Engine::new();
355 BigIntPackage::new().register_into_engine(&mut engine);
356
357 let result: BigInt = engine.eval("parse_bigint(42)").unwrap();
358 assert_eq!(result.to_string(), "42");
359
360 let result: BigInt = engine
361 .eval("parse_bigint(\"123456789012345678901234567890\")")
362 .unwrap();
363 assert_eq!(result.to_string(), "123456789012345678901234567890");
364
365 let result: BigInt = engine.eval("parse_bigint(42) + parse_bigint(58)").unwrap();
366 assert_eq!(result.to_string(), "100");
367
368 let result: bool = engine.eval("parse_bigint(50) > parse_bigint(42)").unwrap();
369 assert!(result);
370
371 let result: bool = engine.eval("parse_bigint(42) == parse_bigint(42)").unwrap();
372 assert!(result);
373
374 let result: bool = engine
375 .eval("parse_bigint(42) != parse_bigint(100)")
376 .unwrap();
377 assert!(result);
378 }
379
380 #[cfg(not(feature = "only_i32"))]
384 #[test]
385 fn test_core_arithmetic_i64_literals() {
386 let mut engine = Engine::new();
387 BigIntPackage::new().register_into_engine(&mut engine);
388
389 let result: BigInt = engine
390 .eval("parse_bigint(1000000000000000000) + parse_bigint(2000000000000000000)")
391 .unwrap();
392 assert_eq!(result.to_string(), "3000000000000000000");
393
394 let result: BigInt = engine
395 .eval("parse_bigint(5000000000000000000) - parse_bigint(1000000000000000000)")
396 .unwrap();
397 assert_eq!(result.to_string(), "4000000000000000000");
398
399 let result: BigInt = engine
400 .eval("parse_bigint(1000000) * parse_bigint(1000000)")
401 .unwrap();
402 assert_eq!(result.to_string(), "1000000000000");
403
404 let result: BigInt = engine
405 .eval("parse_bigint(1000000000000) / parse_bigint(1000000)")
406 .unwrap();
407 assert_eq!(result.to_string(), "1000000");
408
409 let result: BigInt = engine.eval("parse_bigint(10) % parse_bigint(3)").unwrap();
410 assert_eq!(result.to_string(), "1");
411
412 let result: BigInt = engine.eval("-parse_bigint(42)").unwrap();
413 assert_eq!(result.to_string(), "-42");
414 }
415
416 #[cfg(feature = "only_i32")]
419 #[test]
420 fn test_only_i32_int_dispatch_boundaries() {
421 let mut engine = Engine::new();
422 BigIntPackage::new().register_into_engine(&mut engine);
423
424 let result: BigInt = engine.eval("parse_bigint(2147483647)").unwrap();
426 assert_eq!(result.to_string(), "2147483647");
427
428 let result: BigInt = engine.eval("parse_bigint(-2147483648)").unwrap();
429 assert_eq!(result.to_string(), "-2147483648");
430
431 let result: BigInt = engine.eval("(2147483647).to_bigint()").unwrap();
433 assert_eq!(result.to_string(), "2147483647");
434
435 let result: BigInt = engine.eval("parse_bigint(2) ** 30").unwrap();
437 assert_eq!(result.to_string(), "1073741824");
438
439 let result: BigInt = engine.eval("parse_bigint(1) << 31").unwrap();
441 assert_eq!(result.to_string(), "2147483648");
442 }
443
444 #[cfg(feature = "only_i32")]
447 #[test]
448 fn test_core_arithmetic_string_literals() {
449 let mut engine = Engine::new();
450 BigIntPackage::new().register_into_engine(&mut engine);
451
452 let result: BigInt = engine
453 .eval(r#"parse_bigint("1000000000000000000") + parse_bigint("2000000000000000000")"#)
454 .unwrap();
455 assert_eq!(result.to_string(), "3000000000000000000");
456
457 let result: BigInt = engine
458 .eval(r#"parse_bigint("5000000000000000000") - parse_bigint("1000000000000000000")"#)
459 .unwrap();
460 assert_eq!(result.to_string(), "4000000000000000000");
461
462 let result: BigInt = engine
463 .eval(r#"parse_bigint("1000000") * parse_bigint("1000000")"#)
464 .unwrap();
465 assert_eq!(result.to_string(), "1000000000000");
466
467 let result: BigInt = engine
468 .eval(r#"parse_bigint("1000000000000") / parse_bigint("1000000")"#)
469 .unwrap();
470 assert_eq!(result.to_string(), "1000000");
471
472 let result: BigInt = engine
473 .eval(r#"parse_bigint("10") % parse_bigint("3")"#)
474 .unwrap();
475 assert_eq!(result.to_string(), "1");
476
477 let result: BigInt = engine.eval(r#"-parse_bigint("42")"#).unwrap();
478 assert_eq!(result.to_string(), "-42");
479 }
480
481 #[test]
482 fn test_error_handling() {
483 let mut engine = Engine::new();
484 BigIntPackage::new().register_into_engine(&mut engine);
485
486 let result = engine.eval::<BigInt>("parse_bigint(\"not_a_number\")");
487 assert!(result.is_err());
488
489 let result = engine.eval::<BigInt>("parse_bigint(42) / parse_bigint(0)");
490 assert!(result.is_err());
491
492 let result = engine.eval::<BigInt>("parse_bigint(42) % parse_bigint(0)");
493 assert!(result.is_err());
494 }
495
496 #[test]
497 fn test_parse_bigint_from_f64() {
498 let mut engine = Engine::new();
499 BigIntPackage::new().register_into_engine(&mut engine);
500
501 let result: BigInt = engine.eval("parse_bigint(1.5)").unwrap();
503 assert_eq!(result.to_string(), "1");
504
505 let result: BigInt = engine.eval("parse_bigint(-2.9)").unwrap();
506 assert_eq!(result.to_string(), "-2");
507
508 let result: BigInt = engine.eval("parse_bigint(42.0)").unwrap();
510 assert_eq!(result.to_string(), "42");
511
512 let result: BigInt = engine.eval("parse_bigint(1e30)").unwrap();
514 assert_eq!(result.to_string(), "1000000000000000019884624838656");
515 }
516
517 #[test]
518 fn test_parse_bigint_from_f64_errors() {
519 let mut engine = Engine::new();
520 BigIntPackage::new().register_into_engine(&mut engine);
521
522 let result = engine.eval::<BigInt>("parse_bigint(1.0 / 0.0)");
523 assert!(result.is_err(), "infinity should be rejected");
524
525 let result = engine.eval::<BigInt>("parse_bigint(0.0 / 0.0)");
526 assert!(result.is_err(), "NaN should be rejected");
527 }
528
529 #[test]
530 fn test_to_string() {
531 let mut engine = Engine::new();
532 BigIntPackage::new().register_into_engine(&mut engine);
533
534 let result: String = engine.eval("parse_bigint(42).to_string()").unwrap();
535 assert_eq!(result, "42");
536
537 let result: String = engine.eval("parse_bigint(-99).to_string()").unwrap();
538 assert_eq!(result, "-99");
539
540 let result: String = engine
541 .eval("parse_bigint(\"123456789012345678901234567890\").to_string()")
542 .unwrap();
543 assert_eq!(result, "123456789012345678901234567890");
544 }
545
546 #[test]
547 fn test_to_hex() {
548 let mut engine = Engine::new();
549 BigIntPackage::new().register_into_engine(&mut engine);
550
551 let result: String = engine.eval("parse_bigint(255).to_hex()").unwrap();
552 assert_eq!(result, "0xff");
553
554 let result: String = engine.eval("parse_bigint(0).to_hex()").unwrap();
555 assert_eq!(result, "0x0");
556
557 let result: String = engine.eval("parse_bigint(-255).to_hex()").unwrap();
558 assert_eq!(result, "-0xff");
559
560 let result: String = engine.eval("parse_bigint(256).to_hex()").unwrap();
561 assert_eq!(result, "0x100");
562 }
563
564 #[test]
565 fn test_exponentiation() {
566 let mut engine = Engine::new();
567 BigIntPackage::new().register_into_engine(&mut engine);
568
569 let result: BigInt = engine.eval("parse_bigint(2) ** 10").unwrap();
570 assert_eq!(result.to_string(), "1024");
571
572 let result: BigInt = engine.eval("parse_bigint(10) ** 18").unwrap();
573 assert_eq!(result.to_string(), "1000000000000000000");
574
575 let result: BigInt = engine.eval("parse_bigint(-3) ** 3").unwrap();
576 assert_eq!(result.to_string(), "-27");
577
578 let result: BigInt = engine.eval("parse_bigint(5) ** 0").unwrap();
579 assert_eq!(result.to_string(), "1");
580
581 let result = engine.eval::<BigInt>("parse_bigint(2) ** -1");
583 assert!(result.is_err(), "negative exponent should be rejected");
584 }
585
586 #[test]
587 fn test_to_bigint_method() {
588 let mut engine = Engine::new();
589 BigIntPackage::new().register_into_engine(&mut engine);
590
591 let result: BigInt = engine.eval("42.to_bigint()").unwrap();
593 assert_eq!(result.to_string(), "42");
594
595 let result: BigInt = engine.eval("(-99).to_bigint()").unwrap();
596 assert_eq!(result.to_string(), "-99");
597
598 let result: BigInt = engine.eval("0.to_bigint()").unwrap();
599 assert_eq!(result.to_string(), "0");
600
601 let result: BigInt = engine.eval("1.5.to_bigint()").unwrap();
603 assert_eq!(result.to_string(), "1");
604
605 let result: BigInt = engine.eval("(-2.9).to_bigint()").unwrap();
606 assert_eq!(result.to_string(), "-2");
607
608 let result: BigInt = engine.eval("1e30.to_bigint()").unwrap();
609 assert_eq!(result.to_string(), "1000000000000000019884624838656");
610
611 let result = engine.eval::<BigInt>("(1.0 / 0.0).to_bigint()");
613 assert!(result.is_err(), "infinity should be rejected");
614
615 let result = engine.eval::<BigInt>("(0.0 / 0.0).to_bigint()");
616 assert!(result.is_err(), "NaN should be rejected");
617 }
618
619 #[test]
620 fn test_bitwise_operators() {
621 let mut engine = Engine::new();
622 BigIntPackage::new().register_into_engine(&mut engine);
623
624 let result: BigInt = engine
626 .eval("parse_bigint(0b1100) & parse_bigint(0b1010)")
627 .unwrap();
628 assert_eq!(result.to_string(), "8"); let result: BigInt = engine.eval("parse_bigint(255) & parse_bigint(15)").unwrap();
631 assert_eq!(result.to_string(), "15");
632
633 let result: BigInt = engine
635 .eval("parse_bigint(0b1100) | parse_bigint(0b1010)")
636 .unwrap();
637 assert_eq!(result.to_string(), "14"); let result: BigInt = engine.eval("parse_bigint(240) | parse_bigint(15)").unwrap();
640 assert_eq!(result.to_string(), "255");
641
642 let result: BigInt = engine
644 .eval("parse_bigint(0b1100) ^ parse_bigint(0b1010)")
645 .unwrap();
646 assert_eq!(result.to_string(), "6"); let result: BigInt = engine
649 .eval("parse_bigint(255) ^ parse_bigint(255)")
650 .unwrap();
651 assert_eq!(result.to_string(), "0");
652
653 let result: BigInt = engine.eval("parse_bigint(1) << 10").unwrap();
655 assert_eq!(result.to_string(), "1024");
656
657 let result: BigInt = engine.eval("parse_bigint(1) << 64").unwrap();
658 assert_eq!(result.to_string(), "18446744073709551616");
659
660 let result: BigInt = engine.eval("parse_bigint(1024) >> 3").unwrap();
662 assert_eq!(result.to_string(), "128");
663
664 let result: BigInt = engine.eval("parse_bigint(1) >> 1").unwrap();
665 assert_eq!(result.to_string(), "0");
666
667 let result = engine.eval::<BigInt>("parse_bigint(1) << -1");
669 assert!(result.is_err(), "negative left-shift should be rejected");
670
671 let result = engine.eval::<BigInt>("parse_bigint(1) >> -1");
672 assert!(result.is_err(), "negative right-shift should be rejected");
673 }
674
675 #[test]
676 fn test_cross_type_equality_errors() {
677 let mut engine = Engine::new();
678 BigIntPackage::new().register_into_engine(&mut engine);
679
680 assert_cmp_error(
682 &engine,
683 "parse_bigint(42) == 42",
684 "Cannot compare BigInt with int",
685 );
686 assert_cmp_error(
687 &engine,
688 "parse_bigint(42) != 42",
689 "Cannot compare BigInt with int",
690 );
691 assert_cmp_error(
692 &engine,
693 "42 == parse_bigint(42)",
694 "Cannot compare int with BigInt",
695 );
696 assert_cmp_error(
697 &engine,
698 "42 != parse_bigint(42)",
699 "Cannot compare int with BigInt",
700 );
701
702 assert_cmp_error(
704 &engine,
705 "parse_bigint(42) == 42.0",
706 "Cannot compare BigInt with float",
707 );
708 assert_cmp_error(
709 &engine,
710 "parse_bigint(42) != 42.0",
711 "Cannot compare BigInt with float",
712 );
713 assert_cmp_error(
714 &engine,
715 "42.0 == parse_bigint(42)",
716 "Cannot compare float with BigInt",
717 );
718 assert_cmp_error(
719 &engine,
720 "42.0 != parse_bigint(42)",
721 "Cannot compare float with BigInt",
722 );
723
724 assert_cmp_error(
726 &engine,
727 r#"parse_bigint(42) == "42""#,
728 "Cannot compare BigInt with string",
729 );
730 assert_cmp_error(
731 &engine,
732 r#"parse_bigint(42) != "42""#,
733 "Cannot compare BigInt with string",
734 );
735 assert_cmp_error(
736 &engine,
737 r#""42" == parse_bigint(42)"#,
738 "Cannot compare string with BigInt",
739 );
740 assert_cmp_error(
741 &engine,
742 r#""42" != parse_bigint(42)"#,
743 "Cannot compare string with BigInt",
744 );
745
746 assert_cmp_error(
748 &engine,
749 "parse_bigint(1) == true",
750 "Cannot compare BigInt with bool",
751 );
752 assert_cmp_error(
753 &engine,
754 "parse_bigint(1) != true",
755 "Cannot compare BigInt with bool",
756 );
757 assert_cmp_error(
758 &engine,
759 "true == parse_bigint(1)",
760 "Cannot compare bool with BigInt",
761 );
762 assert_cmp_error(
763 &engine,
764 "true != parse_bigint(1)",
765 "Cannot compare bool with BigInt",
766 );
767
768 assert!(engine
770 .eval::<bool>("parse_bigint(42) == parse_bigint(42)")
771 .unwrap());
772 assert!(!engine
773 .eval::<bool>("parse_bigint(42) != parse_bigint(42)")
774 .unwrap());
775 }
776
777 #[test]
778 fn test_cross_type_ordering_errors() {
779 let mut engine = Engine::new();
780 BigIntPackage::new().register_into_engine(&mut engine);
781
782 for op in ["<", "<=", ">", ">="] {
784 assert_cmp_error(
785 &engine,
786 &format!("parse_bigint(42) {op} 42"),
787 "Cannot compare BigInt with int",
788 );
789 assert_cmp_error(
790 &engine,
791 &format!("42 {op} parse_bigint(42)"),
792 "Cannot compare int with BigInt",
793 );
794 }
795
796 for op in ["<", "<=", ">", ">="] {
798 assert_cmp_error(
799 &engine,
800 &format!("parse_bigint(42) {op} 42.0"),
801 "Cannot compare BigInt with float",
802 );
803 assert_cmp_error(
804 &engine,
805 &format!("42.0 {op} parse_bigint(42)"),
806 "Cannot compare float with BigInt",
807 );
808 }
809
810 for op in ["<", "<=", ">", ">="] {
812 assert_cmp_error(
813 &engine,
814 &format!(r#"parse_bigint(42) {op} "42""#),
815 "Cannot compare BigInt with string",
816 );
817 assert_cmp_error(
818 &engine,
819 &format!(r#""42" {op} parse_bigint(42)"#),
820 "Cannot compare string with BigInt",
821 );
822 }
823
824 for op in ["<", "<=", ">", ">="] {
826 assert_cmp_error(
827 &engine,
828 &format!("parse_bigint(1) {op} true"),
829 "Cannot compare BigInt with bool",
830 );
831 assert_cmp_error(
832 &engine,
833 &format!("true {op} parse_bigint(1)"),
834 "Cannot compare bool with BigInt",
835 );
836 }
837
838 assert!(engine
840 .eval::<bool>("parse_bigint(1) < parse_bigint(2)")
841 .unwrap());
842 assert!(engine
843 .eval::<bool>("parse_bigint(2) > parse_bigint(1)")
844 .unwrap());
845 assert!(engine
846 .eval::<bool>("parse_bigint(1) <= parse_bigint(1)")
847 .unwrap());
848 assert!(engine
849 .eval::<bool>("parse_bigint(1) >= parse_bigint(1)")
850 .unwrap());
851 }
852
853 #[test]
856 fn test_cross_type_string_variants() {
857 let mut engine = Engine::new();
858 BigIntPackage::new().register_into_engine(&mut engine);
859
860 assert_cmp_error(
863 &engine,
864 r#"parse_bigint(42) == "42""#,
865 "Cannot compare BigInt with string",
866 );
867
868 assert_cmp_error(
870 &engine,
871 r#"let s = "42"; parse_bigint(42) == s"#,
872 "Cannot compare BigInt with string",
873 );
874 assert_cmp_error(
875 &engine,
876 r#"let s = "42"; s == parse_bigint(42)"#,
877 "Cannot compare string with BigInt",
878 );
879
880 assert_cmp_error(
882 &engine,
883 "parse_bigint(42) == 42.to_string()",
884 "Cannot compare BigInt with string",
885 );
886 assert_cmp_error(
887 &engine,
888 "42.to_string() == parse_bigint(42)",
889 "Cannot compare string with BigInt",
890 );
891
892 assert_cmp_error(
894 &engine,
895 "parse_bigint(42) == parse_bigint(42).to_string()",
896 "Cannot compare BigInt with string",
897 );
898
899 assert_cmp_error(
901 &engine,
902 "parse_bigint(42) == `${42}`",
903 "Cannot compare BigInt with string",
904 );
905 assert_cmp_error(
906 &engine,
907 "`${42}` == parse_bigint(42)",
908 "Cannot compare string with BigInt",
909 );
910 }
911
912 #[test]
913 fn test_to_float() {
914 let mut engine = Engine::new();
915 BigIntPackage::new().register_into_engine(&mut engine);
916
917 let result: rhai::FLOAT = engine.eval("parse_bigint(42).to_float()").unwrap();
918 assert_eq!(result, 42.0);
919
920 let result: rhai::FLOAT = engine.eval("parse_bigint(-7).to_float()").unwrap();
921 assert_eq!(result, -7.0);
922
923 let result = engine.eval::<rhai::FLOAT>(
925 "parse_bigint(\"999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999\").to_float()"
926 );
927 assert!(result.is_err(), "overflow to infinity should be rejected");
928 }
929}