starlark/values/types/int/
globals.rs

1/*
2 * Copyright 2019 The Starlark in Rust Authors.
3 * Copyright (c) Facebook, Inc. and its affiliates.
4 *
5 * Licensed under the Apache License, Version 2.0 (the "License");
6 * you may not use this file except in compliance with the License.
7 * You may obtain a copy of the License at
8 *
9 *     https://www.apache.org/licenses/LICENSE-2.0
10 *
11 * Unless required by applicable law or agreed to in writing, software
12 * distributed under the License is distributed on an "AS IS" BASIS,
13 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14 * See the License for the specific language governing permissions and
15 * limitations under the License.
16 */
17
18use either::Either;
19use starlark_derive::starlark_module;
20
21use crate as starlark;
22use crate::environment::GlobalsBuilder;
23use crate::values::int::int_or_big::StarlarkInt;
24use crate::values::int::pointer_i32::PointerI32;
25use crate::values::types::num::value::NumRef;
26use crate::values::Heap;
27use crate::values::ValueOf;
28use crate::values::ValueOfUnchecked;
29
30#[starlark_module]
31pub(crate) fn register_int(globals: &mut GlobalsBuilder) {
32    /// [int](
33    /// https://github.com/bazelbuild/starlark/blob/master/spec.md#int
34    /// ): convert a value to integer.
35    ///
36    /// `int(x[, base])` interprets its argument as an integer.
37    ///
38    /// If x is an `int`, the result is x.
39    /// If x is a `float`, the result is the integer value nearest to x,
40    /// truncating towards zero; it is an error if x is not finite (`NaN`,
41    /// `+Inf`, `-Inf`).
42    /// If x is a `bool`, the result is 0 for `False` or 1 for `True`.
43    ///
44    /// If x is a string, it is interpreted like a string literal;
45    /// an optional base prefix (`0`, `0b`, `0B`, `0x`, `0X`) determines which
46    /// base to use. The string may specify an arbitrarily large integer,
47    /// whereas true integer literals are restricted to 64 bits.
48    /// If a non-zero `base` argument is provided, the string is interpreted
49    /// in that base and no base prefix is permitted; the base argument may
50    /// specified by name.
51    ///
52    /// `int()` with no arguments returns 0.
53    ///
54    /// ```
55    /// # starlark::assert::all_true(r#"
56    /// int() == 0
57    /// int(1) == 1
58    /// int(False) == 0
59    /// int(True) == 1
60    /// int('1') == 1
61    /// int('16') == 16
62    /// int('16', 10) == 16
63    /// int('16', 8) == 14
64    /// int('16', 16) == 22
65    /// int(0.0) == 0
66    /// int(3.14) == 3
67    /// int(-12345.6789) == -12345
68    /// int(2e9) == 2000000000
69    /// # "#);
70    /// # starlark::assert::fail(r#"
71    /// int("hello")   # error: Cannot parse
72    /// # "#, "Cannot parse");
73    /// # starlark::assert::fail(r#"
74    /// int(float("nan"))   # error: cannot be represented as exact integer
75    /// # "#, "cannot be represented as exact integer");
76    /// # starlark::assert::fail(r#"
77    /// int(float("inf"))   # error: cannot be represented as exact integer
78    /// # "#, "cannot be represented as exact integer");
79    /// ```
80    #[starlark(as_type = PointerI32, speculative_exec_safe)]
81    fn int<'v>(
82        #[starlark(require = pos)] a: Option<
83            ValueOf<'v, Either<Either<NumRef<'v>, bool>, &'v str>>,
84        >,
85        base: Option<i32>,
86        heap: &'v Heap,
87    ) -> starlark::Result<ValueOfUnchecked<'v, StarlarkInt>> {
88        let Some(a) = a else {
89            return Ok(ValueOfUnchecked::new(heap.alloc(0)));
90        };
91        let num_or_bool = match a.typed {
92            Either::Left(num_or_bool) => num_or_bool,
93            Either::Right(s) => {
94                let base = base.unwrap_or(0);
95                if base == 1 || base < 0 || base > 36 {
96                    return Err(anyhow::anyhow!(
97                        "{} is not a valid base, int() base must be >= 2 and <= 36",
98                        base
99                    )
100                    .into());
101                }
102                let (negate, s) = {
103                    match s.chars().next() {
104                        Some('+') => (false, s.get(1..).unwrap()),
105                        Some('-') => (true, s.get(1..).unwrap()),
106                        _ => (false, s),
107                    }
108                };
109                let base = if base == 0 {
110                    match s.get(0..2) {
111                        Some("0b") | Some("0B") => 2,
112                        Some("0o") | Some("0O") => 8,
113                        Some("0x") | Some("0X") => 16,
114                        _ => 10,
115                    }
116                } else {
117                    base as u32
118                };
119                let s = match base {
120                    16 => {
121                        if s.starts_with("0x") || s.starts_with("0X") {
122                            s.get(2..).unwrap()
123                        } else {
124                            s
125                        }
126                    }
127                    8 => {
128                        if s.starts_with("0o") || s.starts_with("0O") {
129                            s.get(2..).unwrap()
130                        } else {
131                            s
132                        }
133                    }
134                    2 => {
135                        if s.starts_with("0b") || s.starts_with("0B") {
136                            s.get(2..).unwrap()
137                        } else {
138                            s
139                        }
140                    }
141                    _ => s,
142                };
143                // We already handled the sign above, so we are not trying to parse another sign.
144                if s.starts_with('-') || s.starts_with('+') {
145                    return Err(anyhow::anyhow!("Cannot parse `{}` as an integer", s,).into());
146                }
147
148                let x = StarlarkInt::from_str_radix(s, base)?;
149                let x = if negate { -x } else { x };
150                return Ok(ValueOfUnchecked::new(heap.alloc(x)));
151            }
152        };
153
154        if let Some(base) = base {
155            return Err(anyhow::anyhow!(
156                "int() cannot convert non-string with explicit base '{}'",
157                base
158            )
159            .into());
160        }
161
162        match num_or_bool {
163            Either::Left(NumRef::Int(_)) => Ok(ValueOfUnchecked::new(a.value)),
164            Either::Left(NumRef::Float(f)) => Ok(ValueOfUnchecked::new(
165                heap.alloc(StarlarkInt::from_f64_exact(f.0.trunc())?),
166            )),
167            Either::Right(b) => Ok(ValueOfUnchecked::new(heap.alloc(b as i32))),
168        }
169    }
170}