1use crate::compiler::prelude::*;
2
3fn parse_int(value: &Value, base: Option<Value>) -> Resolved {
4 let string = value.try_bytes_utf8_lossy()?;
5 let (base, index) = match base {
6 Some(base) => {
7 let base = base.try_integer()?;
8
9 if !(2..=36).contains(&base) {
10 return Err(format!(
11 "invalid base {value}: must be be between 2 and 36 (inclusive)"
12 )
13 .into());
14 }
15 #[allow(clippy::cast_sign_loss, clippy::cast_possible_truncation)]
17 (base as u32, 0)
18 }
19 None => match string.chars().next() {
20 Some('0') => match string.chars().nth(1) {
21 Some('b') => (2, 2),
22 Some('o') => (8, 2),
23 Some('x') => (16, 2),
24 _ => (8, 0),
25 },
26 Some(_) => (10u32, 0),
27 None => return Err("value is empty".into()),
28 },
29 };
30 let converted = i64::from_str_radix(&string[index..], base)
31 .map_err(|err| format!("could not parse integer: {err}"))?;
32
33 Ok(converted.into())
34}
35
36#[derive(Clone, Copy, Debug)]
37pub struct ParseInt;
38
39impl Function for ParseInt {
40 fn identifier(&self) -> &'static str {
41 "parse_int"
42 }
43
44 fn usage(&self) -> &'static str {
45 "Parses the string `value` representing a number in an optional base/radix to an integer."
46 }
47
48 fn category(&self) -> &'static str {
49 Category::Parse.as_ref()
50 }
51
52 fn internal_failure_reasons(&self) -> &'static [&'static str] {
53 &[
54 "The base is not between 2 and 36.",
55 "The number cannot be parsed in the base.",
56 ]
57 }
58
59 fn return_kind(&self) -> u16 {
60 kind::INTEGER
61 }
62
63 fn parameters(&self) -> &'static [Parameter] {
64 const PARAMETERS: &[Parameter] = &[
65 Parameter::required("value", kind::BYTES, "The string to parse."),
66 Parameter::optional(
67 "base",
68 kind::INTEGER,
69 "The base the number is in. Must be between 2 and 36 (inclusive).
70
71If unspecified, the string prefix is used to
72determine the base: \"0b\", 8 for \"0\" or \"0o\", 16 for \"0x\",
73and 10 otherwise.",
74 ),
75 ];
76 PARAMETERS
77 }
78
79 fn examples(&self) -> &'static [Example] {
80 &[
81 example! {
82 title: "Parse decimal",
83 source: r#"parse_int!("-42")"#,
84 result: Ok("-42"),
85 },
86 example! {
87 title: "Parse binary",
88 source: r#"parse_int!("0b1001")"#,
89 result: Ok("9"),
90 },
91 example! {
92 title: "Parse octal",
93 source: r#"parse_int!("0o42")"#,
94 result: Ok("34"),
95 },
96 example! {
97 title: "Parse hexadecimal",
98 source: r#"parse_int!("0x2a")"#,
99 result: Ok("42"),
100 },
101 example! {
102 title: "Parse explicit base",
103 source: r#"parse_int!("2a", 17)"#,
104 result: Ok("44"),
105 },
106 ]
107 }
108
109 fn compile(
110 &self,
111 _state: &state::TypeState,
112 _ctx: &mut FunctionCompileContext,
113 arguments: ArgumentList,
114 ) -> Compiled {
115 let value = arguments.required("value");
116 let base = arguments.optional("base");
117
118 Ok(ParseIntFn { value, base }.as_expr())
119 }
120}
121
122#[derive(Debug, Clone)]
123struct ParseIntFn {
124 value: Box<dyn Expression>,
125 base: Option<Box<dyn Expression>>,
126}
127
128impl FunctionExpression for ParseIntFn {
129 fn resolve(&self, ctx: &mut Context) -> Resolved {
130 let value = self.value.resolve(ctx)?;
131 let base = self
132 .base
133 .as_ref()
134 .map(|expr| expr.resolve(ctx))
135 .transpose()?;
136
137 parse_int(&value, base)
138 }
139
140 fn type_def(&self, _: &state::TypeState) -> TypeDef {
141 TypeDef::integer().fallible()
142 }
143}
144
145#[cfg(test)]
146mod tests {
147 use super::*;
148
149 test_function![
150 parse_int => ParseInt;
151
152 decimal {
153 args: func_args![value: "-42"],
154 want: Ok(-42),
155 tdef: TypeDef::integer().fallible(),
156 }
157
158 binary {
159 args: func_args![value: "0b1001"],
160 want: Ok(9),
161 tdef: TypeDef::integer().fallible(),
162 }
163
164 octal {
165 args: func_args![value: "042"],
166 want: Ok(34),
167 tdef: TypeDef::integer().fallible(),
168 }
169
170 hexadecimal {
171 args: func_args![value: "0x2a"],
172 want: Ok(42),
173 tdef: TypeDef::integer().fallible(),
174 }
175
176 zero {
177 args: func_args![value: "0"],
178 want: Ok(0),
179 tdef: TypeDef::integer().fallible(),
180 }
181
182 explicit_hexadecimal {
183 args: func_args![value: "2a", base: 16],
184 want: Ok(42),
185 tdef: TypeDef::integer().fallible(),
186 }
187 ];
188}