Skip to main content

typst_library/foundations/
int.rs

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