cairo_lang_syntax/node/
ast_ext.rs

1//! Custom methods for AST node types.
2//!
3//! The impls here are visible through [`super`] module.
4
5use cairo_lang_utils::require;
6use num_bigint::{BigInt, Sign};
7use num_traits::Num;
8use smol_str::SmolStr;
9use unescaper::unescape;
10
11use super::{
12    TerminalFalse, TerminalLiteralNumber, TerminalShortString, TerminalString, TerminalTrue,
13};
14use crate::node::Terminal;
15use crate::node::db::SyntaxGroup;
16
17impl TerminalTrue {
18    #[inline(always)]
19    pub fn boolean_value(&self) -> bool {
20        true
21    }
22}
23
24impl TerminalFalse {
25    #[inline(always)]
26    pub fn boolean_value(&self) -> bool {
27        false
28    }
29}
30
31impl TerminalLiteralNumber {
32    /// Interpret this terminal as a [`BigInt`] number.
33    pub fn numeric_value(&self, db: &dyn SyntaxGroup) -> Option<BigInt> {
34        self.numeric_value_and_suffix(db).map(|(value, _suffix)| value)
35    }
36
37    /// Interpret this terminal as a [`BigInt`] number and get the suffix if this literal has one.
38    pub fn numeric_value_and_suffix(
39        &self,
40        db: &dyn SyntaxGroup,
41    ) -> Option<(BigInt, Option<SmolStr>)> {
42        let text = self.text(db);
43
44        let (text, radix) = if let Some(num_no_prefix) = text.strip_prefix("0x") {
45            (num_no_prefix, 16)
46        } else if let Some(num_no_prefix) = text.strip_prefix("0o") {
47            (num_no_prefix, 8)
48        } else if let Some(num_no_prefix) = text.strip_prefix("0b") {
49            (num_no_prefix, 2)
50        } else {
51            (text.as_str(), 10)
52        };
53
54        // Catch an edge case, where literal seems to have a suffix that is valid numeric part
55        // according to the radix. Interpret this as an untyped number.
56        // Example: 0x1_f32 is interpreted as 0x1F32 without suffix.
57        if let Ok(value) = BigInt::from_str_radix(text, radix) {
58            Some((value, None))
59        } else {
60            let (text, suffix) = match text.rsplit_once('_') {
61                Some((text, suffix)) => {
62                    let suffix = if suffix.is_empty() { None } else { Some(suffix) };
63                    (text, suffix)
64                }
65                None => (text, None),
66            };
67            Some((BigInt::from_str_radix(text, radix).ok()?, suffix.map(SmolStr::new)))
68        }
69    }
70}
71
72impl TerminalShortString {
73    /// Interpret this token/terminal as a string.
74    pub fn string_value(&self, db: &dyn SyntaxGroup) -> Option<String> {
75        let text = self.text(db);
76
77        let (text, _suffix) = string_value(&text, '\'')?;
78
79        Some(text)
80    }
81
82    /// Interpret this terminal as a [`BigInt`] number.
83    pub fn numeric_value(&self, db: &dyn SyntaxGroup) -> Option<BigInt> {
84        self.string_value(db).map(|string| BigInt::from_bytes_be(Sign::Plus, string.as_bytes()))
85    }
86
87    /// Get suffix from this literal if it has one.
88    pub fn suffix(&self, db: &dyn SyntaxGroup) -> Option<SmolStr> {
89        let text = self.text(db);
90        let (_literal, mut suffix) = text[1..].rsplit_once('\'')?;
91        require(!suffix.is_empty())?;
92        if suffix.starts_with('_') {
93            suffix = &suffix[1..];
94        }
95        Some(suffix.into())
96    }
97}
98
99impl TerminalString {
100    /// Interpret this token/terminal as a string.
101    pub fn string_value(&self, db: &dyn SyntaxGroup) -> Option<String> {
102        let text = self.text(db);
103        let (text, suffix) = string_value(&text, '"')?;
104        if !suffix.is_empty() {
105            unreachable!();
106        }
107
108        Some(text)
109    }
110}
111
112/// Interpret the given text as a string with the given delimiter. Returns the text and the suffix.
113fn string_value(text: &str, delimiter: char) -> Option<(String, &str)> {
114    let (prefix, text) = text.split_once(delimiter)?;
115    if !prefix.is_empty() {
116        unreachable!();
117    }
118
119    let (text, suffix) = text.rsplit_once(delimiter)?;
120
121    let text = unescape(text).ok()?;
122
123    require(text.is_ascii())?;
124
125    Some((text, suffix))
126}