artichoke_backend/extn/core/integer/
mod.rs1use std::fmt::{self, Write as _};
2
3use crate::extn::core::numeric::{self, Coercion, Outcome};
4use crate::extn::prelude::*;
5use crate::fmt::WriteError;
6
7pub(in crate::extn) mod mruby;
8pub(super) mod trampoline;
9
10#[repr(transparent)]
11#[derive(Default, Clone, Copy, Hash, PartialEq, Eq, PartialOrd, Ord)]
12pub struct Integer(i64);
13
14impl fmt::Debug for Integer {
15 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
16 self.0.fmt(f)
17 }
18}
19
20impl fmt::Display for Integer {
21 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
22 self.0.fmt(f)
23 }
24}
25
26impl Convert<Integer, Value> for Artichoke {
27 #[inline]
28 fn convert(&self, from: Integer) -> Value {
29 self.convert(from.0)
30 }
31}
32
33impl TryConvert<Value, Integer> for Artichoke {
34 type Error = Error;
35
36 #[inline]
37 fn try_convert(&self, value: Value) -> Result<Integer, Self::Error> {
38 let num = self.try_convert(value)?;
39 Ok(Integer(num))
40 }
41}
42
43impl From<i64> for Integer {
44 #[inline]
45 fn from(int: i64) -> Self {
46 Self(int)
47 }
48}
49
50impl From<Integer> for i64 {
51 #[inline]
52 fn from(int: Integer) -> Self {
53 int.as_i64()
54 }
55}
56
57impl From<Integer> for f64 {
58 #[inline]
59 fn from(int: Integer) -> Self {
60 int.as_f64()
61 }
62}
63
64impl From<Integer> for Outcome {
65 #[inline]
66 fn from(int: Integer) -> Self {
67 Self::Integer(int.into())
68 }
69}
70
71impl From<i64> for Outcome {
72 #[inline]
73 fn from(int: i64) -> Self {
74 Self::Integer(int)
75 }
76}
77
78impl Integer {
79 #[inline]
81 #[must_use]
82 pub const fn new() -> Self {
83 Self(0)
84 }
85
86 #[inline]
87 #[must_use]
88 pub const fn as_i64(self) -> i64 {
89 self.0
90 }
91
92 #[inline]
93 #[must_use]
94 #[expect(clippy::cast_precision_loss, reason = "Ruby Integer#to_f is intentionally lossy")]
95 pub const fn as_f64(self) -> f64 {
96 self.0 as f64
97 }
98
99 pub fn chr(self, interp: &mut Artichoke, encoding: Option<Value>) -> Result<spinoso_string::String, Error> {
100 if let Some(encoding) = encoding {
101 let mut message = b"encoding parameter of Integer#chr (given ".to_vec();
102 message.extend(encoding.inspect(interp));
103 message.extend_from_slice(b") not supported");
104 Err(NotImplementedError::from(message).into())
105 } else {
106 match u8::try_from(self.as_i64()) {
134 Ok(chr @ 0..=127) => Ok(spinoso_string::String::ascii(vec![chr])),
136 Ok(chr @ 128..=255) => Ok(spinoso_string::String::binary(vec![chr])),
138 Err(_) => {
139 let mut message = String::new();
140 write!(&mut message, "{} out of char range", self.as_i64()).map_err(WriteError::from)?;
141 Err(RangeError::from(message).into())
142 }
143 }
144 }
145 }
146
147 #[inline]
148 pub fn bit(self, bit: i64) -> Self {
149 if let Ok(bit) = u32::try_from(bit) {
150 self.as_i64().checked_shr(bit).map_or(0, |v| v & 1).into()
151 } else {
152 Self(0)
153 }
154 }
155
156 pub fn div(self, interp: &mut Artichoke, denominator: Value) -> Result<Outcome, Error> {
157 match denominator.ruby_type() {
158 Ruby::Fixnum => {
159 let denom = denominator.try_convert_into::<i64>(interp)?;
160 let value = self.as_i64();
161 if denom == 0 {
162 Err(ZeroDivisionError::with_message("divided by 0").into())
163 } else if value < 0 && (value % denom) != 0 {
164 Ok(((value / denom) - 1).into())
165 } else {
166 Ok((value / denom).into())
167 }
168 }
169 Ruby::Float => {
170 let denom = denominator.try_convert_into::<f64>(interp)?;
171 Ok((self.as_f64() / denom).into())
172 }
173 _ => {
174 let x = interp.convert(self);
175 let coerced = numeric::coerce(interp, x, denominator)?;
176 match coerced {
177 Coercion::Float(_, 0.0) | Coercion::Integer(_, 0) => {
178 Err(ZeroDivisionError::with_message("divided by 0").into())
179 }
180 Coercion::Float(numer, denom) => Ok((numer / denom).into()),
181 Coercion::Integer(numer, denom) if numer < 0 && (numer % denom) != 0 => {
182 Ok(((numer / denom) - 1).into())
183 }
184 Coercion::Integer(numer, denom) => Ok((numer / denom).into()),
185 }
186 }
187 }
188 }
189
190 #[must_use]
191 pub const fn is_allbits(self, mask: i64) -> bool {
192 self.as_i64() & mask == mask
193 }
194
195 #[must_use]
196 pub const fn is_anybits(self, mask: i64) -> bool {
197 self.as_i64() & mask != 0
198 }
199
200 #[must_use]
201 pub const fn is_nobits(self, mask: i64) -> bool {
202 self.as_i64() & mask == 0
203 }
204
205 #[must_use]
206 pub const fn size() -> usize {
207 const { size_of::<i64>() }
208 }
209}
210
211#[cfg(test)]
212mod tests {
213 use crate::extn::prelude::*;
214 use crate::test::prelude::*;
215
216 #[test]
217 fn positive_integer_division_vm_opcode() {
218 let mut interp = interpreter();
219 run_arbitrary::<(u8, u8)>(|(x, y)| match (x, y) {
220 (0, 0) => {
221 assert!(interp.eval(b"0 / 0").is_err());
222 }
223 (x, 0) | (0, x) => {
224 let expr = format!("{x} / 0");
225 assert!(
226 interp.eval(expr.as_bytes()).is_err(),
227 "expected error for division by zero: {expr}"
228 );
229 let expr = format!("0 / {x}");
230 let quotient = interp
231 .eval(expr.as_bytes())
232 .unwrap()
233 .try_convert_into::<i64>(&interp)
234 .unwrap();
235 assert_eq!(quotient, 0);
236 }
237 (x, y) => {
238 let expr = format!("{x} / {y}");
239 let quotient = interp
240 .eval(expr.as_bytes())
241 .unwrap()
242 .try_convert_into::<i64>(&interp)
243 .unwrap();
244 let expected = i64::from(x) / i64::from(y);
245 assert_eq!(quotient, expected);
246 }
247 });
248 }
249
250 #[test]
251 fn positive_integer_division_send() {
252 let mut interp = interpreter();
253 run_arbitrary::<(u8, u8)>(|(x, y)| match (x, y) {
254 (0, 0) => {
255 assert!(interp.eval(b"0.send('/', 0)").is_err());
256 }
257 (x, 0) | (0, x) => {
258 let expr = format!("{x}.send('/', 0)");
259 assert!(
260 interp.eval(expr.as_bytes()).is_err(),
261 "expected error for division by zero: {expr}"
262 );
263 let expr = format!("0.send('/', {x})");
264 let quotient = interp
265 .eval(expr.as_bytes())
266 .unwrap()
267 .try_convert_into::<i64>(&interp)
268 .unwrap();
269 assert_eq!(quotient, 0);
270 }
271 (x, y) => {
272 let expr = format!("{x}.send('/', {y})");
273 let quotient = interp
274 .eval(expr.as_bytes())
275 .unwrap()
276 .try_convert_into::<i64>(&interp)
277 .unwrap();
278 let expected = i64::from(x) / i64::from(y);
279 assert_eq!(quotient, expected);
280 }
281 });
282 }
283
284 #[test]
285 fn negative_integer_division_vm_opcode() {
286 let mut interp = interpreter();
287 run_arbitrary::<(u8, u8)>(|(x, y)| match (x, y) {
288 (0, 0) => {
289 assert!(interp.eval(b"-0 / 0").is_err());
290 }
291 (x, 0) | (0, x) => {
292 let expr = format!("-{x} / 0");
293 assert!(
294 interp.eval(expr.as_bytes()).is_err(),
295 "expected error for division by zero: {expr}"
296 );
297 let expr = format!("0 / -{x}");
298 let quotient = interp
299 .eval(expr.as_bytes())
300 .unwrap()
301 .try_convert_into::<i64>(&interp)
302 .unwrap();
303 assert_eq!(quotient, 0);
304 }
305 (x, y) => {
306 let expr = format!("-{x} / {y}");
307 let quotient = interp
308 .eval(expr.as_bytes())
309 .unwrap()
310 .try_convert_into::<i64>(&interp)
311 .unwrap();
312 if x % y == 0 {
313 let expected = -i64::from(x) / i64::from(y);
314 assert_eq!(quotient, expected);
315 } else {
316 let expected = (-i64::from(x) / i64::from(y)) - 1;
317 assert_eq!(quotient, expected);
318 }
319 }
320 });
321 }
322
323 #[test]
324 fn negative_integer_division_send() {
325 let mut interp = interpreter();
326 run_arbitrary::<(u8, u8)>(|(x, y)| match (x, y) {
327 (0, 0) => {
328 assert!(interp.eval(b"-0.send('/', 0)").is_err());
329 }
330 (x, 0) | (0, x) => {
331 let expr = format!("-{x}.send('/', 0)");
332 assert!(
333 interp.eval(expr.as_bytes()).is_err(),
334 "expected error for division by zero: {expr}"
335 );
336 let expr = format!("0.send('/', -{x})");
337 let quotient = interp
338 .eval(expr.as_bytes())
339 .unwrap()
340 .try_convert_into::<i64>(&interp)
341 .unwrap();
342 assert_eq!(quotient, 0);
343 }
344 (x, y) => {
345 let expr = format!("-{x}.send('/', {y})");
346 let quotient = interp
347 .eval(expr.as_bytes())
348 .unwrap()
349 .try_convert_into::<i64>(&interp)
350 .unwrap();
351 if x % y == 0 {
352 let expected = -i64::from(x) / i64::from(y);
353 assert_eq!(quotient, expected);
354 } else {
355 let expected = (-i64::from(x) / i64::from(y)) - 1;
356 assert_eq!(quotient, expected);
357 }
358 }
359 });
360 }
361}