1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
//! Custom methods for AST node types.
//!
//! The impls here are visible through [`super`] module.

use num_bigint::{BigInt, Sign};
use num_traits::Num;
use smol_str::SmolStr;
use unescaper::unescape;

use super::{
    TerminalFalse, TerminalLiteralNumber, TerminalShortString, TerminalString, TerminalTrue,
};
use crate::node::db::SyntaxGroup;
use crate::node::Terminal;

impl TerminalTrue {
    #[inline(always)]
    pub fn boolean_value(&self) -> bool {
        true
    }
}

impl TerminalFalse {
    #[inline(always)]
    pub fn boolean_value(&self) -> bool {
        false
    }
}

impl TerminalLiteralNumber {
    /// Interpret this terminal as a [`BigInt`] number.
    pub fn numeric_value(&self, db: &dyn SyntaxGroup) -> Option<BigInt> {
        self.numeric_value_and_suffix(db).map(|(value, _suffix)| value)
    }

    /// Interpret this terminal as a [`BigInt`] number and get the suffix if this literal has one.
    pub fn numeric_value_and_suffix(
        &self,
        db: &dyn SyntaxGroup,
    ) -> Option<(BigInt, Option<SmolStr>)> {
        let text = self.text(db);

        let (text, radix) = if let Some(num_no_prefix) = text.strip_prefix("0x") {
            (num_no_prefix, 16)
        } else if let Some(num_no_prefix) = text.strip_prefix("0o") {
            (num_no_prefix, 8)
        } else if let Some(num_no_prefix) = text.strip_prefix("0b") {
            (num_no_prefix, 2)
        } else {
            (text.as_str(), 10)
        };

        // Catch an edge case, where literal seems to have a suffix that is valid numeric part
        // according to the radix. Interpret this as an untyped number.
        // Example: 0x1_f32 is interpreted as 0x1F32 without suffix.
        if let Ok(value) = BigInt::from_str_radix(text, radix) {
            Some((value, None))
        } else {
            let (text, suffix) = match text.rsplit_once('_') {
                Some((text, suffix)) => {
                    let suffix = if suffix.is_empty() { None } else { Some(suffix) };
                    (text, suffix)
                }
                None => (text, None),
            };
            Some((BigInt::from_str_radix(text, radix).ok()?, suffix.map(SmolStr::new)))
        }
    }
}

impl TerminalShortString {
    /// Interpret this token/terminal as a string.
    pub fn string_value(&self, db: &dyn SyntaxGroup) -> Option<String> {
        let text = self.text(db);

        let (text, _suffix) = string_value(&text, '\'')?;

        Some(text)
    }

    /// Interpret this terminal as a [`BigInt`] number.
    pub fn numeric_value(&self, db: &dyn SyntaxGroup) -> Option<BigInt> {
        self.string_value(db).map(|string| BigInt::from_bytes_be(Sign::Plus, string.as_bytes()))
    }

    /// Get suffix from this literal if it has one.
    pub fn suffix(&self, db: &dyn SyntaxGroup) -> Option<SmolStr> {
        let text = self.text(db);
        let (_literal, mut suffix) = text[1..].rsplit_once('\'')?;
        if suffix.is_empty() {
            return None;
        }
        if suffix.starts_with('_') {
            suffix = &suffix[1..];
        }
        Some(suffix.into())
    }
}

impl TerminalString {
    /// Interpret this token/terminal as a string.
    pub fn string_value(&self, db: &dyn SyntaxGroup) -> Option<String> {
        let text = self.text(db);
        let (text, suffix) = string_value(&text, '"')?;
        if !suffix.is_empty() {
            unreachable!();
        }

        Some(text)
    }
}

/// Interpret the given text as a string with the given delimiter. Returns the text and the suffix.
fn string_value(text: &str, delimiter: char) -> Option<(String, &str)> {
    let Some((prefix, text)) = text.split_once(delimiter) else {
        return None;
    };
    if !prefix.is_empty() {
        unreachable!();
    }

    let Some((text, suffix)) = text.rsplit_once(delimiter) else {
        return None;
    };

    let text = unescape(text).ok()?;

    if !text.is_ascii() {
        return None;
    }

    Some((text, suffix))
}