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}