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_filesystem::ids::SmolStrId;
6use cairo_lang_utils::require;
7use num_bigint::{BigInt, Sign};
8use num_traits::Num;
9use salsa::Database;
10use unescaper::unescape;
11
12use super::{
13    TerminalFalse, TerminalLiteralNumber, TerminalShortString, TerminalString, TerminalTrue,
14};
15use crate::node::Terminal;
16
17impl<'a> TerminalTrue<'a> {
18    #[inline(always)]
19    pub fn boolean_value(&self) -> bool {
20        true
21    }
22}
23
24impl<'a> TerminalFalse<'a> {
25    #[inline(always)]
26    pub fn boolean_value(&self) -> bool {
27        false
28    }
29}
30
31impl<'a> TerminalLiteralNumber<'a> {
32    /// Interpret this terminal as a [`BigInt`] number.
33    pub fn numeric_value(&self, db: &'a dyn Database) -> 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: &'a dyn Database,
41    ) -> Option<(BigInt, Option<SmolStrId<'a>>)> {
42        let text = self.text(db).long(db).as_str();
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, 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((
68                BigInt::from_str_radix(text, radix).ok()?,
69                suffix.map(|s| SmolStrId::from(db, s)),
70            ))
71        }
72    }
73}
74
75impl<'a> TerminalShortString<'a> {
76    /// Interpret this token/terminal as a string.
77    pub fn string_value(&self, db: &'a dyn Database) -> Option<String> {
78        let text = self.text(db).long(db);
79
80        let (text, _suffix) = string_value(text, '\'')?;
81
82        Some(text)
83    }
84
85    /// Interpret this terminal as a [`BigInt`] number.
86    pub fn numeric_value(&self, db: &'a dyn Database) -> Option<BigInt> {
87        self.string_value(db).map(|string| BigInt::from_bytes_be(Sign::Plus, string.as_bytes()))
88    }
89
90    /// Get suffix from this literal if it has one.
91    pub fn suffix(&self, db: &'a dyn Database) -> Option<&'a str> {
92        let text = self.text(db).long(db);
93        let (_literal, mut suffix) = text[1..].rsplit_once('\'')?;
94        require(!suffix.is_empty())?;
95        if suffix.starts_with('_') {
96            suffix = &suffix[1..];
97        }
98        Some(suffix)
99    }
100}
101
102impl<'a> TerminalString<'a> {
103    /// Interpret this token/terminal as a string.
104    pub fn string_value(&self, db: &'a dyn Database) -> Option<String> {
105        let text = self.text(db).long(db);
106        let (text, suffix) = string_value(text, '"')?;
107        if !suffix.is_empty() {
108            unreachable!();
109        }
110
111        Some(text)
112    }
113}
114
115/// Interpret the given text as a string with the given delimiter. Returns the text and the suffix.
116fn string_value(text: &str, delimiter: char) -> Option<(String, &str)> {
117    let (prefix, text) = text.split_once(delimiter)?;
118    if !prefix.is_empty() {
119        unreachable!();
120    }
121
122    let (text, suffix) = text.rsplit_once(delimiter)?;
123
124    let text = unescape(text).ok()?;
125
126    require(text.is_ascii())?;
127
128    Some((text, suffix))
129}