typst_library/foundations/
int.rs

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