rune/ast/
lit_number.rs

1use crate::ast::prelude::*;
2use crate::compile::{num, WithSpan};
3
4use ast::token::NumberSize;
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        let span = self.span;
64
65        let text = match self.source {
66            ast::NumberSource::Synthetic(id) => {
67                let Some(number) = cx.storage.get_number(id) else {
68                    return Err(compile::Error::new(
69                        span,
70                        ErrorKind::BadSyntheticId {
71                            kind: SyntheticKind::Number,
72                            id,
73                        },
74                    ));
75                };
76
77                return Ok((*number).try_clone()?);
78            }
79            ast::NumberSource::Text(text) => text,
80        };
81
82        let string = cx
83            .sources
84            .source(text.source_id, text.number)
85            .ok_or_else(|| compile::Error::new(span, ErrorKind::BadSlice))?;
86
87        let suffix = cx
88            .sources
89            .source(text.source_id, text.suffix)
90            .ok_or_else(|| compile::Error::new(span, ErrorKind::BadSlice))?;
91
92        let suffix = match suffix {
93            "u8" => Some(ast::NumberSuffix::Unsigned(text.suffix, NumberSize::S8)),
94            "u16" => Some(ast::NumberSuffix::Unsigned(text.suffix, NumberSize::S16)),
95            "u32" => Some(ast::NumberSuffix::Unsigned(text.suffix, NumberSize::S32)),
96            "u64" => Some(ast::NumberSuffix::Unsigned(text.suffix, NumberSize::S64)),
97            "i8" => Some(ast::NumberSuffix::Signed(text.suffix, NumberSize::S8)),
98            "i16" => Some(ast::NumberSuffix::Signed(text.suffix, NumberSize::S16)),
99            "i32" => Some(ast::NumberSuffix::Signed(text.suffix, NumberSize::S32)),
100            "i64" => Some(ast::NumberSuffix::Signed(text.suffix, NumberSize::S64)),
101            "f32" | "f64" => Some(ast::NumberSuffix::Float(text.suffix)),
102            "" => None,
103            _ => {
104                return Err(compile::Error::new(
105                    text.suffix,
106                    ErrorKind::UnsupportedSuffix,
107                ))
108            }
109        };
110
111        if matches!(
112            (suffix, text.is_fractional),
113            (Some(ast::NumberSuffix::Float(..)), _) | (None, true)
114        ) {
115            let number: f64 = num::from_float(cx.scratch, string).with_span(span)?;
116
117            return Ok(ast::Number {
118                value: ast::NumberValue::Float(number),
119                suffix,
120            });
121        }
122
123        let parser = match text.base {
124            ast::NumberBase::Binary => num::from_ascii_binary,
125            ast::NumberBase::Octal => num::from_ascii_octal,
126            ast::NumberBase::Hex => num::from_ascii_hex,
127            ast::NumberBase::Decimal => num::from_ascii_decimal,
128        };
129
130        let number = parser(string.as_bytes())
131            .ok_or_else(|| ErrorKind::BadNumberLiteral)
132            .with_span(span)?;
133
134        Ok(ast::Number {
135            value: ast::NumberValue::Integer(number as i128),
136            suffix,
137        })
138    }
139}
140
141impl ToTokens for LitNumber {
142    fn to_tokens(
143        &self,
144        _: &mut MacroContext<'_, '_, '_>,
145        stream: &mut TokenStream,
146    ) -> alloc::Result<()> {
147        stream.push(ast::Token {
148            span: self.span,
149            kind: ast::Kind::Number(self.source),
150        })
151    }
152}