typst_library/foundations/
int.rs

1use std::num::{
2    NonZeroI64, NonZeroIsize, NonZeroU32, NonZeroU64, NonZeroUsize, ParseIntError,
3};
4
5use ecow::{EcoString, eco_format};
6use smallvec::SmallVec;
7
8use crate::diag::{StrResult, bail};
9use crate::foundations::{
10    Bytes, Cast, Decimal, Repr, Str, Value, cast, func, repr, scope, ty,
11};
12
13/// A whole number.
14///
15/// The number can be negative, zero, or positive. As Typst uses 64 bits to
16/// store integers, integers cannot be smaller than `{-9223372036854775808}` or
17/// larger than `{9223372036854775807}`. Integer literals are always positive,
18/// so a negative integer such as `{-1}` is semantically the negation `-` of the
19/// positive literal `1`. A positive integer greater than the maximum value and
20/// a negative integer less than or equal to the minimum value cannot be
21/// represented as an integer literal, and are instead parsed as a `{float}`.
22/// The minimum integer value can still be obtained through integer arithmetic.
23///
24/// The number can also be specified as hexadecimal, octal, or binary by
25/// starting it with a zero followed by either `x`, `o`, or `b`.
26///
27/// You can convert a value to an integer with this type's constructor.
28///
29/// # Example
30/// ```example
31/// #(1 + 2) \
32/// #(2 - 5) \
33/// #(3 + 4 < 8)
34///
35/// #0xff \
36/// #0o10 \
37/// #0b1001
38/// ```
39#[ty(scope, cast, name = "int", title = "Integer")]
40type i64;
41
42#[scope]
43impl i64 {
44    /// Converts a value to an integer. Raises an error if there is an attempt
45    /// to produce an integer larger than the maximum 64-bit signed integer
46    /// or smaller than the minimum 64-bit signed integer.
47    ///
48    /// - Booleans are converted to `0` or `1`.
49    /// - Floats and decimals are rounded to the next 64-bit integer towards zero.
50    /// - Strings are parsed in base 10.
51    ///
52    /// ```example
53    /// #int(false) \
54    /// #int(true) \
55    /// #int(2.7) \
56    /// #int(decimal("3.8")) \
57    /// #(int("27") + int("4"))
58    /// ```
59    #[func(constructor)]
60    pub fn construct(
61        /// The value that should be converted to an integer.
62        value: ToInt,
63    ) -> i64 {
64        value.0
65    }
66
67    /// Calculates the sign of an integer.
68    ///
69    /// - If the number is positive, returns `{1}`.
70    /// - If the number is negative, returns `{-1}`.
71    /// - If the number is zero, returns `{0}`.
72    ///
73    /// ```example
74    /// #(5).signum() \
75    /// #(-5).signum() \
76    /// #(0).signum()
77    /// ```
78    #[func]
79    pub fn signum(self) -> i64 {
80        i64::signum(self)
81    }
82
83    /// Calculates the bitwise NOT of an integer.
84    ///
85    /// For the purposes of this function, the operand is treated as a signed
86    /// integer of 64 bits.
87    ///
88    /// ```example
89    /// #4.bit-not() \
90    /// #(-1).bit-not()
91    /// ```
92    #[func(title = "Bitwise NOT")]
93    pub fn bit_not(self) -> i64 {
94        !self
95    }
96
97    /// Calculates the bitwise AND between two integers.
98    ///
99    /// For the purposes of this function, the operands are treated as signed
100    /// integers of 64 bits.
101    ///
102    /// ```example
103    /// #128.bit-and(192)
104    /// ```
105    #[func(title = "Bitwise AND")]
106    pub fn bit_and(
107        self,
108        /// The right-hand operand of the bitwise AND.
109        rhs: i64,
110    ) -> i64 {
111        self & rhs
112    }
113
114    /// Calculates the bitwise OR between two integers.
115    ///
116    /// For the purposes of this function, the operands are treated as signed
117    /// integers of 64 bits.
118    ///
119    /// ```example
120    /// #64.bit-or(32)
121    /// ```
122    #[func(title = "Bitwise OR")]
123    pub fn bit_or(
124        self,
125        /// The right-hand operand of the bitwise OR.
126        rhs: i64,
127    ) -> i64 {
128        self | rhs
129    }
130
131    /// Calculates the bitwise XOR between two integers.
132    ///
133    /// For the purposes of this function, the operands are treated as signed
134    /// integers of 64 bits.
135    ///
136    /// ```example
137    /// #64.bit-xor(96)
138    /// ```
139    #[func(title = "Bitwise XOR")]
140    pub fn bit_xor(
141        self,
142        /// The right-hand operand of the bitwise XOR.
143        rhs: i64,
144    ) -> i64 {
145        self ^ rhs
146    }
147
148    /// Shifts the operand's bits to the left by the specified amount.
149    ///
150    /// For the purposes of this function, the operand is treated as a signed
151    /// integer of 64 bits. An error will occur if the result is too large to
152    /// fit in a 64-bit integer.
153    ///
154    /// ```example
155    /// #33.bit-lshift(2) \
156    /// #(-1).bit-lshift(3)
157    /// ```
158    #[func(title = "Bitwise Left Shift")]
159    pub fn bit_lshift(
160        self,
161        /// The amount of bits to shift. Must not be negative.
162        shift: u32,
163    ) -> StrResult<i64> {
164        Ok(self.checked_shl(shift).ok_or("the result is too large")?)
165    }
166
167    /// Shifts the operand's bits to the right by the specified amount.
168    /// Performs an arithmetic shift by default (extends the sign bit to the left,
169    /// such that negative numbers stay negative), but that can be changed by the
170    /// `logical` parameter.
171    ///
172    /// For the purposes of this function, the operand is treated as a signed
173    /// integer of 64 bits.
174    ///
175    /// ```example
176    /// #64.bit-rshift(2) \
177    /// #(-8).bit-rshift(2) \
178    /// #(-8).bit-rshift(2, logical: true)
179    /// ```
180    #[func(title = "Bitwise Right Shift")]
181    pub fn bit_rshift(
182        self,
183        /// The amount of bits to shift. Must not be negative.
184        ///
185        /// Shifts larger than 63 are allowed and will cause the return value to
186        /// saturate. For non-negative numbers, the return value saturates at
187        /// `{0}`, while, for negative numbers, it saturates at `{-1}` if
188        /// `logical` is set to `{false}`, or `{0}` if it is `{true}`. This
189        /// behavior is consistent with just applying this operation multiple
190        /// times. Therefore, the shift will always succeed.
191        shift: u32,
192        /// Toggles whether a logical (unsigned) right shift should be performed
193        /// instead of arithmetic right shift.
194        /// If this is `{true}`, negative operands will not preserve their sign
195        /// bit, and bits which appear to the left after the shift will be
196        /// `{0}`. This parameter has no effect on non-negative operands.
197        #[named]
198        #[default(false)]
199        logical: bool,
200    ) -> i64 {
201        if logical {
202            if shift >= u64::BITS {
203                // Excessive logical right shift would be equivalent to setting
204                // all bits to zero. Using `.min(63)` is not enough for logical
205                // right shift, since `-1 >> 63` returns 1, whereas
206                // `calc.bit-rshift(-1, 64)` should return the same as
207                // `(-1 >> 63) >> 1`, which is zero.
208                0
209            } else {
210                // Here we reinterpret the signed integer's bits as unsigned to
211                // perform logical right shift, and then reinterpret back as signed.
212                // This is valid as, according to the Rust reference, casting between
213                // two integers of same size (i64 <-> u64) is a no-op (two's complement
214                // is used).
215                // Reference:
216                // https://doc.rust-lang.org/stable/reference/expressions/operator-expr.html#numeric-cast
217                ((self as u64) >> shift) as i64
218            }
219        } else {
220            // Saturate at -1 (negative) or 0 (otherwise) on excessive arithmetic
221            // right shift. Shifting those numbers any further does not change
222            // them, so it is consistent.
223            let shift = shift.min(i64::BITS - 1);
224            self >> shift
225        }
226    }
227
228    /// Converts bytes to an integer.
229    ///
230    /// ```example
231    /// #int.from-bytes(bytes((0, 0, 0, 0, 0, 0, 0, 1))) \
232    /// #int.from-bytes(bytes((1, 0, 0, 0, 0, 0, 0, 0)), endian: "big")
233    /// ```
234    #[func]
235    pub fn from_bytes(
236        /// The bytes that should be converted to an integer.
237        ///
238        /// Must be of length at most 8 so that the result fits into a 64-bit
239        /// signed integer.
240        bytes: Bytes,
241        /// The endianness of the conversion.
242        #[named]
243        #[default(Endianness::Little)]
244        endian: Endianness,
245        /// Whether the bytes should be treated as a signed integer. If this is
246        /// `{true}` and the most significant bit is set, the resulting number
247        /// will negative.
248        #[named]
249        #[default(true)]
250        signed: bool,
251    ) -> StrResult<i64> {
252        let len = bytes.len();
253        if len == 0 {
254            return Ok(0);
255        } else if len > 8 {
256            bail!("too many bytes to convert to a 64 bit number");
257        }
258
259        // `decimal` will hold the part of the buffer that should be filled with
260        // the input bytes, `rest` will remain as is or be filled with 0xFF for
261        // negative numbers if signed is true.
262        //
263        // – big-endian: `decimal` will be the rightmost bytes of the buffer.
264        // - little-endian: `decimal` will be the leftmost bytes of the buffer.
265        let mut buf = [0u8; 8];
266        let (rest, decimal) = match endian {
267            Endianness::Big => buf.split_at_mut(8 - len),
268            Endianness::Little => {
269                let (first, second) = buf.split_at_mut(len);
270                (second, first)
271            }
272        };
273
274        decimal.copy_from_slice(bytes.as_ref());
275
276        // Perform sign-extension if necessary.
277        if signed {
278            let most_significant_byte = match endian {
279                Endianness::Big => decimal[0],
280                Endianness::Little => decimal[len - 1],
281            };
282
283            if most_significant_byte & 0b1000_0000 != 0 {
284                rest.fill(0xFF);
285            }
286        }
287
288        Ok(match endian {
289            Endianness::Big => i64::from_be_bytes(buf),
290            Endianness::Little => i64::from_le_bytes(buf),
291        })
292    }
293
294    /// Converts an integer to bytes.
295    ///
296    /// ```example
297    /// #array(10000.to-bytes(endian: "big")) \
298    /// #array(10000.to-bytes(size: 4))
299    /// ```
300    #[func]
301    pub fn to_bytes(
302        self,
303        /// The endianness of the conversion.
304        #[named]
305        #[default(Endianness::Little)]
306        endian: Endianness,
307        /// The size in bytes of the resulting bytes (must be at least zero). If
308        /// the integer is too large to fit in the specified size, the
309        /// conversion will truncate the remaining bytes based on the
310        /// endianness. To keep the same resulting value, if the endianness is
311        /// big-endian, the truncation will happen at the rightmost bytes.
312        /// Otherwise, if the endianness is little-endian, the truncation will
313        /// happen at the leftmost bytes.
314        ///
315        /// Be aware that if the integer is negative and the size is not enough
316        /// to make the number fit, when passing the resulting bytes to
317        /// `int.from-bytes`, the resulting number might be positive, as the
318        /// most significant bit might not be set to 1.
319        #[named]
320        #[default(8)]
321        size: usize,
322    ) -> Bytes {
323        let array = match endian {
324            Endianness::Big => self.to_be_bytes(),
325            Endianness::Little => self.to_le_bytes(),
326        };
327
328        let mut buf = SmallVec::<[u8; 8]>::from_elem(0, size);
329        match endian {
330            Endianness::Big => {
331                // Copy the bytes from the array to the buffer, starting from
332                // the end of the buffer.
333                let buf_start = size.saturating_sub(8);
334                let array_start = 8usize.saturating_sub(size);
335                buf[buf_start..].copy_from_slice(&array[array_start..])
336            }
337            Endianness::Little => {
338                // Copy the bytes from the array to the buffer, starting from
339                // the beginning of the buffer.
340                let end = size.min(8);
341                buf[..end].copy_from_slice(&array[..end])
342            }
343        }
344
345        Bytes::new(buf)
346    }
347}
348
349impl Repr for i64 {
350    fn repr(&self) -> EcoString {
351        eco_format!("{:?}", self)
352    }
353}
354
355/// Represents the byte order used for converting integers and floats to bytes
356/// and vice versa.
357#[derive(Debug, Copy, Clone, Eq, PartialEq, Hash, Cast)]
358pub enum Endianness {
359    /// Big-endian byte order: The highest-value byte is at the beginning of the
360    /// bytes.
361    Big,
362    /// Little-endian byte order: The lowest-value byte is at the beginning of
363    /// the bytes.
364    Little,
365}
366
367/// A value that can be cast to an integer.
368pub struct ToInt(i64);
369
370cast! {
371    ToInt,
372    v: i64 => Self(v),
373    v: bool => Self(v as i64),
374    v: f64 => Self(convert_float_to_int(v)?),
375    v: Decimal => Self(i64::try_from(v).map_err(|_| eco_format!("number too large"))?),
376    v: Str => Self(parse_int(&v).map_err(|_| eco_format!("invalid integer: {}", v))?),
377}
378
379pub fn convert_float_to_int(f: f64) -> StrResult<i64> {
380    if f <= i64::MIN as f64 - 1.0 || f >= i64::MAX as f64 + 1.0 {
381        Err(eco_format!("number too large"))
382    } else {
383        Ok(f as i64)
384    }
385}
386
387fn parse_int(mut s: &str) -> Result<i64, ParseIntError> {
388    let mut sign = 1;
389    if let Some(rest) = s.strip_prefix('-').or_else(|| s.strip_prefix(repr::MINUS_SIGN)) {
390        sign = -1;
391        s = rest;
392    }
393    if sign == -1 && s == "9223372036854775808" {
394        return Ok(i64::MIN);
395    }
396    Ok(sign * s.parse::<i64>()?)
397}
398
399macro_rules! signed_int {
400    ($($ty:ty)*) => {
401        $(cast! {
402            $ty,
403            self => {
404                #[allow(irrefutable_let_patterns)]
405                if let Ok(int) = i64::try_from(self) {
406                    Value::Int(int)
407                } else {
408                    // Some numbers (i128) are too large to be cast as i64
409                    // In that case, we accept that there may be a
410                    // precision loss, and use a floating point number
411                    Value::Float(self as _)
412                }
413            },
414            v: i64 => v.try_into().map_err(|_| "number too large")?,
415        })*
416    }
417}
418
419macro_rules! unsigned_int {
420    ($($ty:ty)*) => {
421        $(cast! {
422            $ty,
423            self => {
424                #[allow(irrefutable_let_patterns)]
425                if let Ok(int) = i64::try_from(self) {
426                    Value::Int(int)
427                } else {
428                    // Some numbers (u64, u128) are too large to be cast as i64
429                    // In that case, we accept that there may be a
430                    // precision loss, and use a floating point number
431                    Value::Float(self as _)
432                }
433            },
434            v: i64 => v.try_into().map_err(|_| {
435                if v < 0 {
436                    "number must be at least zero"
437                } else {
438                    "number too large"
439                }
440            })?,
441        })*
442    }
443}
444
445signed_int! { i8 i16 i32 i128 isize }
446unsigned_int! { u8 u16 u32 u64 u128 usize }
447
448cast! {
449    NonZeroI64,
450    self => Value::Int(self.get() as _),
451    v: i64 => v.try_into()
452        .map_err(|_| if v == 0 {
453            "number must not be zero"
454        } else {
455            "number too large"
456        })?,
457}
458
459cast! {
460    NonZeroIsize,
461    self => Value::Int(self.get() as _),
462    v: i64 => v
463        .try_into()
464        .and_then(|v: isize| v.try_into())
465        .map_err(|_| if v == 0 {
466            "number must not be zero"
467        } else {
468            "number too large"
469        })?,
470}
471
472cast! {
473    NonZeroU64,
474    self => Value::Int(self.get() as _),
475    v: i64 => v
476        .try_into()
477        .and_then(|v: u64| v.try_into())
478        .map_err(|_| if v <= 0 {
479            "number must be positive"
480        } else {
481            "number too large"
482        })?,
483}
484
485cast! {
486    NonZeroUsize,
487    self => Value::Int(self.get() as _),
488    v: i64 => v
489        .try_into()
490        .and_then(|v: usize| v.try_into())
491        .map_err(|_| if v <= 0 {
492            "number must be positive"
493        } else {
494            "number too large"
495        })?,
496}
497
498cast! {
499    NonZeroU32,
500    self => Value::Int(self.get() as _),
501    v: i64 => v
502        .try_into()
503        .and_then(|v: u32| v.try_into())
504        .map_err(|_| if v <= 0 {
505            "number must be positive"
506        } else {
507            "number too large"
508        })?,
509}