rune/ast/
lit_number.rs

1use crate::ast::prelude::*;
2
3use ast::token::NumberSize;
4use num::Num;
5
6#[test]
7#[cfg(not(miri))]
8fn ast_parse() {
9    rt::<ast::LitNumber>("42");
10    rt::<ast::LitNumber>("42.42");
11    rt::<ast::LitNumber>("0.42");
12    rt::<ast::LitNumber>("0.42e10");
13}
14
15/// A number literal.
16///
17/// * `42`.
18/// * `4.2e10`.
19#[derive(Debug, TryClone, Clone, Copy, PartialEq, Eq, Spanned)]
20#[try_clone(copy)]
21#[non_exhaustive]
22pub struct LitNumber {
23    /// The span corresponding to the literal.
24    pub span: Span,
25    /// The source of the number.
26    #[rune(skip)]
27    pub source: ast::NumberSource,
28}
29
30impl ToAst for LitNumber {
31    fn to_ast(span: Span, kind: ast::Kind) -> compile::Result<Self> {
32        match kind {
33            K![number(source)] => Ok(LitNumber { source, span }),
34            _ => Err(compile::Error::expected(
35                ast::Token { span, kind },
36                Self::into_expectation(),
37            )),
38        }
39    }
40
41    #[inline]
42    fn matches(kind: &ast::Kind) -> bool {
43        matches!(kind, K![number])
44    }
45
46    #[inline]
47    fn into_expectation() -> Expectation {
48        Expectation::Description("number")
49    }
50}
51
52impl Parse for LitNumber {
53    fn parse(parser: &mut Parser<'_>) -> Result<Self> {
54        let t = parser.next()?;
55        Self::to_ast(t.span, t.kind)
56    }
57}
58
59impl<'a> Resolve<'a> for LitNumber {
60    type Output = ast::Number;
61
62    fn resolve(&self, cx: ResolveContext<'a>) -> Result<ast::Number> {
63        fn err_span<E>(span: Span) -> impl Fn(E) -> compile::Error {
64            move |_| compile::Error::new(span, ErrorKind::BadNumberLiteral)
65        }
66
67        let span = self.span;
68
69        let text = match self.source {
70            ast::NumberSource::Synthetic(id) => {
71                let Some(number) = cx.storage.get_number(id) else {
72                    return Err(compile::Error::new(
73                        span,
74                        ErrorKind::BadSyntheticId {
75                            kind: SyntheticKind::Number,
76                            id,
77                        },
78                    ));
79                };
80
81                return Ok((*number).try_clone()?);
82            }
83            ast::NumberSource::Text(text) => text,
84        };
85
86        let string = cx
87            .sources
88            .source(text.source_id, text.number)
89            .ok_or_else(|| compile::Error::new(span, ErrorKind::BadSlice))?;
90
91        let suffix = cx
92            .sources
93            .source(text.source_id, text.suffix)
94            .ok_or_else(|| compile::Error::new(span, ErrorKind::BadSlice))?;
95
96        let suffix = match suffix {
97            "u8" => Some(ast::NumberSuffix::Unsigned(text.suffix, NumberSize::S8)),
98            "u16" => Some(ast::NumberSuffix::Unsigned(text.suffix, NumberSize::S16)),
99            "u32" => Some(ast::NumberSuffix::Unsigned(text.suffix, NumberSize::S32)),
100            "u64" => Some(ast::NumberSuffix::Unsigned(text.suffix, NumberSize::S64)),
101            "i8" => Some(ast::NumberSuffix::Signed(text.suffix, NumberSize::S8)),
102            "i16" => Some(ast::NumberSuffix::Signed(text.suffix, NumberSize::S16)),
103            "i32" => Some(ast::NumberSuffix::Signed(text.suffix, NumberSize::S32)),
104            "i64" => Some(ast::NumberSuffix::Signed(text.suffix, NumberSize::S64)),
105            "f32" | "f64" => Some(ast::NumberSuffix::Float(text.suffix)),
106            "" => None,
107            _ => {
108                return Err(compile::Error::new(
109                    text.suffix,
110                    ErrorKind::UnsupportedSuffix,
111                ))
112            }
113        };
114
115        if matches!(
116            (suffix, text.is_fractional),
117            (Some(ast::NumberSuffix::Float(..)), _) | (None, true)
118        ) {
119            let number: f64 = string
120                .trim_matches(|c: char| c == '_')
121                .parse()
122                .map_err(err_span(span))?;
123
124            return Ok(ast::Number {
125                value: ast::NumberValue::Float(number),
126                suffix,
127            });
128        }
129
130        let radix = match text.base {
131            ast::NumberBase::Binary => 2,
132            ast::NumberBase::Octal => 8,
133            ast::NumberBase::Hex => 16,
134            ast::NumberBase::Decimal => 10,
135        };
136
137        let number = num::BigInt::from_str_radix(string, radix).map_err(err_span(span))?;
138
139        Ok(ast::Number {
140            value: ast::NumberValue::Integer(number),
141            suffix,
142        })
143    }
144}
145
146impl ToTokens for LitNumber {
147    fn to_tokens(
148        &self,
149        _: &mut MacroContext<'_, '_, '_>,
150        stream: &mut TokenStream,
151    ) -> alloc::Result<()> {
152        stream.push(ast::Token {
153            span: self.span,
154            kind: ast::Kind::Number(self.source),
155        })
156    }
157}