qbe_parser/ast/
core.rs

1//! The core AST types.
2
3use chumsky::Parser;
4use ordered_float::OrderedFloat;
5use smol_str::SmolStr;
6use std::borrow::Borrow;
7use std::cmp::Ordering;
8use std::fmt::{self, Debug, Display, Formatter, Write};
9use std::hash::Hash;
10use std::ops::Deref;
11use std::sync::OnceLock;
12use unicode_ident::{is_xid_continue, is_xid_start};
13
14pub use crate::ast::Span;
15use crate::lexer::{Keyword, ShortTypeSpec, Token, TokenParser};
16use crate::parse::{Parse, impl_fromstr_via_parse};
17
18// NOTE: Derive macros for Ord, Eq, Hash ignore Span, only consider value
19#[derive(Copy, Clone, Hash, Eq, PartialEq, Ord, PartialOrd)]
20pub struct Spanned<T> {
21    pub value: T,
22    pub span: Span,
23}
24impl<T> Spanned<T> {
25    #[inline]
26    pub fn unspanned(value: T) -> Self {
27        Spanned {
28            value,
29            span: Span::MISSING,
30        }
31    }
32    #[inline]
33    pub fn map<U>(this: Self, func: impl FnOnce(T) -> U) -> Spanned<U> {
34        Spanned {
35            value: func(this.value),
36            span: this.span,
37        }
38    }
39}
40impl<T: Debug> Debug for Spanned<T> {
41    fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
42        f.debug_tuple("Spanned")
43            .field(&self.value)
44            .field(&self.span)
45            .finish()
46    }
47}
48impl<T: Display> Display for Spanned<T> {
49    fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
50        Display::fmt(&self.value, f)
51    }
52}
53impl<T> From<T> for Spanned<T> {
54    fn from(value: T) -> Self {
55        Spanned {
56            value,
57            span: Span::MISSING,
58        }
59    }
60}
61impl<T> From<(T, Span)> for Spanned<T> {
62    fn from(value: (T, Span)) -> Self {
63        Spanned {
64            value: value.0,
65            span: value.1,
66        }
67    }
68}
69impl<T> Deref for Spanned<T> {
70    type Target = T;
71
72    #[inline]
73    fn deref(&self) -> &Self::Target {
74        &self.value
75    }
76}
77
78/// Implements an opaque wrapper around a [`String`] like [`Ident`] or [`StringLiteral`].
79///
80/// Does not implement [`Display`].
81macro_rules! opaque_string_wrapper {
82    ($target:ident) => {
83        impl $target {
84            #[inline]
85            pub fn text(&self) -> &'_ str {
86                &self.text
87            }
88            #[inline]
89            pub fn span(&self) -> Span {
90                self.span
91            }
92        }
93        impl From<AstString> for $target {
94            fn from(s: AstString) -> Self {
95                $target::new(s, Span::MISSING)
96            }
97        }
98        impl From<String> for $target {
99            fn from(value: String) -> Self {
100                $target::new(value, Span::MISSING)
101            }
102        }
103        impl From<&str> for $target {
104            fn from(value: &str) -> Self {
105                $target::new(value, Span::MISSING)
106            }
107        }
108        impl From<(AstString, Span)> for $target {
109            #[inline]
110            fn from(value: (AstString, Span)) -> Self {
111                $target::new(value.0, value.1)
112            }
113        }
114        impl<T: Into<AstString>> From<Spanned<T>> for $target {
115            fn from(value: Spanned<T>) -> Self {
116                $target::new(value.value, value.span)
117            }
118        }
119        impl_string_like!($target);
120    };
121}
122/// An identifier in the source code.
123#[derive(Clone, Eq, PartialEq, Hash, PartialOrd, Ord)]
124pub struct Ident {
125    text: AstString,
126    span: Span,
127}
128impl Ident {
129    #[track_caller]
130    #[inline]
131    pub fn unspanned(text: impl Into<AstString>) -> Self {
132        Self::new(text, Span::MISSING)
133    }
134    /// Create an identifier from a string.
135    ///
136    /// This accepts keywords without errors.
137    /// Using the `From<Keyword>` implementation does the same thing,
138    /// but more explicitly.
139    ///
140    /// # Panics
141    /// If the characters are not valid, this will panic.
142    #[inline]
143    #[track_caller]
144    pub fn new(text: impl Into<AstString>, span: Span) -> Self {
145        let text = text.into();
146        let mut chars = text.chars();
147        let first = chars
148            .next()
149            .unwrap_or_else(|| panic!("Identifier is empty at {span:?}"));
150        assert!(
151            is_xid_start(first),
152            "Invalid start char {first:?} for ident at {span:?}"
153        );
154        for other in chars {
155            assert!(
156                is_xid_continue(other),
157                "Invalid char {other:?} for ident at {span:?}"
158            );
159        }
160        if let Ok(byte_len) = span.byte_len() {
161            assert_eq!(
162                byte_len,
163                text.len() as u64,
164                "Length of span {span} doesn't match {text:?}"
165            );
166        }
167        Ident { text, span }
168    }
169    #[inline]
170    pub fn is_keyword(&self) -> bool {
171        self.to_keyword().is_some()
172    }
173    #[inline]
174    pub fn to_keyword(&self) -> Option<Keyword> {
175        self.as_str().parse::<Keyword>().ok()
176    }
177    #[inline]
178    pub fn to_short_type_spec(&self) -> Option<ShortTypeSpec> {
179        self.as_str().parse::<ShortTypeSpec>().ok()
180    }
181    /// Get a string representation of this [`Ident`],
182    /// equivalent to the [`Display`] impl.
183    ///
184    /// Not present on prefixed idents like [`TemporaryName`],
185    /// as those also have a prefix.
186    #[inline]
187    pub fn as_str(&self) -> &'_ str {
188        &self.text
189    }
190}
191impl From<Spanned<Keyword>> for Ident {
192    fn from(value: Spanned<Keyword>) -> Self {
193        Ident::new(value.text(), value.span)
194    }
195}
196impl From<Keyword> for Ident {
197    fn from(value: Keyword) -> Self {
198        Spanned::unspanned(value).into()
199    }
200}
201opaque_string_wrapper!(Ident);
202impl Display for Ident {
203    fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
204        f.write_str(&self.text)
205    }
206}
207impl Parse for Ident {
208    const DESC: &'static str = "identifier";
209    fn parser<'a>() -> impl TokenParser<'a, Self> {
210        chumsky::select!(Token::Ident(ref name) => name.clone()).labelled(Self::DESC)
211    }
212}
213
214/// A quoted string literal.
215#[derive(Clone, Eq, PartialEq, Hash, PartialOrd, Ord)]
216pub struct StringLiteral {
217    text: AstString,
218    span: Span,
219}
220impl StringLiteral {
221    #[inline]
222    pub fn unspanned(text: impl Into<AstString>) -> Self {
223        StringLiteral::new(text, Span::MISSING)
224    }
225    #[inline]
226    pub fn new(text: impl Into<AstString>, span: Span) -> Self {
227        StringLiteral {
228            text: text.into(),
229            span,
230        }
231    }
232}
233opaque_string_wrapper!(StringLiteral);
234impl Parse for StringLiteral {
235    const DESC: &'static str = "string literal";
236    fn parser<'a>() -> impl TokenParser<'a, Self> {
237        chumsky::select!(Token::StringLiteral(str) => str).labelled(Self::DESC)
238    }
239}
240impl Display for StringLiteral {
241    fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
242        f.write_char('"')?;
243        for c in self.text.chars().flat_map(char::escape_default) {
244            f.write_char(c)?;
245        }
246        f.write_char('"')
247    }
248}
249
250#[derive(Copy, Clone, Eq, PartialEq, Hash, Debug, Ord, PartialOrd)]
251pub struct NumericLiteral<T: Number> {
252    pub value: T,
253    pub span: Span,
254}
255impl<T: Number> NumericLiteral<T> {
256    #[inline]
257    pub fn unspanned(value: T) -> Self {
258        NumericLiteral {
259            value,
260            span: Span::MISSING,
261        }
262    }
263    #[inline]
264    pub fn span(&self) -> Span {
265        self.span
266    }
267    #[inline]
268    pub fn map_value<U: Number>(self, func: impl FnOnce(T) -> U) -> NumericLiteral<U> {
269        NumericLiteral {
270            value: func(self.value),
271            span: self.span,
272        }
273    }
274}
275impl<T: Number> Display for NumericLiteral<T> {
276    fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
277        Display::fmt(&self.value, f)
278    }
279}
280impl<T: Number> From<T> for NumericLiteral<T> {
281    #[inline]
282    fn from(value: T) -> Self {
283        Self::unspanned(value)
284    }
285}
286impl From<f64> for NumericLiteral<OrderedFloat<f64>> {
287    #[inline]
288    fn from(value: f64) -> Self {
289        Self::unspanned(value.into())
290    }
291}
292impl From<f32> for NumericLiteral<OrderedFloat<f32>> {
293    #[inline]
294    fn from(value: f32) -> Self {
295        Self::unspanned(value.into())
296    }
297}
298
299/// A prefix for a [`FloatLiteral`],
300/// which determines the size of the float.
301#[derive(Copy, Clone, Debug, Eq, PartialEq, Ord, PartialOrd, Hash)]
302pub enum FloatPrefix {
303    SinglePrecision,
304    DoublePrecision,
305}
306impl FloatPrefix {
307    pub fn text(&self) -> &'static str {
308        match self {
309            FloatPrefix::SinglePrecision => "s_",
310            FloatPrefix::DoublePrecision => "d_",
311        }
312    }
313}
314impl Display for FloatPrefix {
315    fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
316        f.write_str(self.text())
317    }
318}
319type FloatValue = NumericLiteral<OrderedFloat<f64>>;
320#[derive(Clone, Eq, PartialEq, Hash, Debug, Ord, PartialOrd)]
321pub struct FloatLiteral {
322    pub span: Span,
323    pub prefix: Spanned<FloatPrefix>,
324    pub value: FloatValue,
325}
326impl FloatLiteral {
327    #[inline]
328    pub fn span(&self) -> Span {
329        self.span
330    }
331    /// Create a single-precision float literal without any span information.
332    pub fn single_unspanned(value: impl Into<FloatValue>) -> Self {
333        FloatLiteral {
334            value: value.into(),
335            span: Span::MISSING,
336            prefix: Spanned::from(FloatPrefix::SinglePrecision),
337        }
338    }
339    /// Create a double-precision float literal without any span information.
340    pub fn double_unspanned(value: impl Into<FloatValue>) -> Self {
341        FloatLiteral {
342            value: value.into(),
343            span: Span::MISSING,
344            prefix: Spanned::from(FloatPrefix::DoublePrecision),
345        }
346    }
347}
348impl Display for FloatLiteral {
349    fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
350        write!(f, "{}{}", self.prefix, self.value)
351    }
352}
353impl Parse for FloatLiteral {
354    const DESC: &'static str = "float literal";
355    fn parser<'a>() -> impl TokenParser<'a, Self> {
356        chumsky::select!(Token::Float(literal) => literal).labelled(Self::DESC)
357    }
358}
359impl_fromstr_via_parse!(FloatLiteral);
360
361/// A type that can be used in a [`NumericLiteral`].
362pub trait Number: Debug + Display + num_traits::Num + Clone {}
363macro_rules! impl_numtype {
364    ($($target:ty),+ $(,)?) => {
365        $(impl Number for $target {})*
366    };
367}
368impl_numtype!(
369    u32,
370    u64,
371    usize,
372    i32,
373    i64,
374    isize,
375    f64,
376    ordered_float::OrderedFloat<f32>,
377    ordered_float::OrderedFloat<f64>,
378    ordered_float::NotNan<f64>,
379    u128,
380    i128,
381);
382
383macro_rules! prefixed_ident_type {
384    ($target:ident, PREFIX = $prefix:literal, DESC = $desc:literal) => {
385        #[derive(Clone, Eq, PartialEq, Hash, PartialOrd, Ord)]
386        pub struct $target {
387            ident: Ident,
388            span: Span,
389        }
390        impl $target {
391            pub const PREFIX: char = $prefix;
392            /// The label used for describing this value.
393            pub(crate) fn label() -> &'static str {
394                static LABEL: OnceLock<Box<str>> = OnceLock::new();
395                &*LABEL.get_or_init(|| {
396                    let snake_name: &str = paste3::paste!(stringify!());
397                    snake_name.replace('_', " ").into_boxed_str()
398                })
399            }
400            #[track_caller]
401            pub fn unspanned(text: &str) -> Self {
402                Self {
403                    ident: Ident::new(text, Span::MISSING),
404                    span: Span::MISSING,
405                }
406            }
407            #[inline]
408            pub fn text(&self) -> &'_ str {
409                self.ident.text()
410            }
411            #[inline]
412            pub fn ident(&self) -> &'_ Ident {
413                &self.ident
414            }
415            #[inline]
416            pub fn span(&self) -> Span {
417                self.span
418            }
419            #[inline]
420            #[track_caller]
421            pub fn new(ident: Ident, span: Span) -> Self {
422                let res = Self { ident, span };
423                assert_eq!(res.ident.span().is_missing(), span.is_missing());
424                if !span.is_missing() {
425                    assert_eq!(
426                        res.ident.span().byte_range().unwrap(),
427                        span.slice_byte_indexes(1..).byte_range().unwrap(),
428                        "Span for {ident:?} doesn't correspond to {res:?}",
429                        ident = res.ident
430                    );
431                }
432                res
433            }
434        }
435        impl Parse for $target {
436            const DESC: &'static str = $desc;
437            fn parser<'a>() -> impl TokenParser<'a, Self> {
438                use chumsky::Parser;
439                chumsky::select!(Token::$target(val) => val).labelled(Self::DESC)
440            }
441        }
442        impl_ident_like!($target);
443        impl Display for $target {
444            fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
445                f.write_char(Self::PREFIX)?;
446                f.write_str(self.text())
447            }
448        }
449    };
450}
451prefixed_ident_type!(TypeName, PREFIX = ':', DESC = "type name");
452prefixed_ident_type!(GlobalName, PREFIX = '$', DESC = "global name");
453prefixed_ident_type!(TemporaryName, PREFIX = '%', DESC = "temporary name");
454prefixed_ident_type!(BlockName, PREFIX = '@', DESC = "block name");
455
456/// An owned string used by the AST.
457///
458/// This is semantically equivalent to the [`String`] type,
459/// but cannot be directly mutated and has different performance characteristics.
460#[derive(Clone, Eq, PartialEq, PartialOrd, Ord, Hash)]
461pub struct AstString(SmolStr);
462impl AstString {
463    /// Create an [`AstString`] from a static string.
464    ///
465    /// This may be able to avoid allocation that would be required when using the [`From`] impl.
466    /// However, this is not guaranteed.
467    #[inline]
468    pub fn from_static(s: &'static str) -> AstString {
469        AstString(SmolStr::new_static(s))
470    }
471    #[inline]
472    pub fn as_str(&self) -> &'_ str {
473        self.0.as_str()
474    }
475}
476impl From<String> for AstString {
477    #[inline]
478    fn from(s: String) -> Self {
479        AstString(s.into())
480    }
481}
482impl From<&str> for AstString {
483    #[inline]
484    fn from(s: &str) -> Self {
485        AstString(s.into())
486    }
487}
488impl From<AstString> for String {
489    #[inline]
490    fn from(value: AstString) -> Self {
491        value.0.into()
492    }
493}
494impl Debug for AstString {
495    fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
496        <str as Debug>::fmt(&self.0, f)
497    }
498}
499impl Display for AstString {
500    fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
501        f.write_str(self.as_str())
502    }
503}
504impl Deref for AstString {
505    type Target = str;
506    #[inline]
507    fn deref(&self) -> &Self::Target {
508        self.0.as_str()
509    }
510}
511impl Borrow<str> for AstString {
512    #[inline]
513    fn borrow(&self) -> &str {
514        self.0.as_str()
515    }
516}
517impl equivalent::Equivalent<String> for AstString {
518    fn equivalent(&self, other: &String) -> bool {
519        self.as_str() == other.as_str()
520    }
521}
522impl equivalent::Comparable<String> for AstString {
523    fn compare(&self, key: &String) -> Ordering {
524        self.as_str().cmp(key.as_str())
525    }
526}