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}