compose_syntax/ast/
atomics.rs

1use crate::ast::macros::node;
2use crate::ast::{Expr, Statement};
3use crate::SyntaxNode;
4use ecow::EcoString;
5use std::ops::Deref;
6use unscanny::Scanner;
7
8node! {
9    struct Ident
10}
11
12impl<'a> Ident<'a> {
13    pub fn get(self) -> &'a EcoString {
14        self.0.text()
15    }
16
17    pub fn as_str(self) -> &'a str {
18        self.get()
19    }
20}
21
22impl Deref for Ident<'_> {
23    type Target = str;
24
25    /// Dereference to a string. Note that this shortens the lifetime, so you
26    /// may need to use [`get()`](Self::get) instead in some situations.
27    fn deref(&self) -> &Self::Target {
28        self.as_str()
29    }
30}
31
32node! {
33    /// A boolean: `true`, `false`.
34    struct Bool
35}
36
37impl Bool<'_> {
38    /// Get the boolean value.
39    pub fn get(self) -> bool {
40        self.0.text() == "true"
41    }
42}
43
44node! {
45    struct Int
46}
47
48impl Int<'_> {
49    pub fn get(self) -> i64 {
50        self.0.text().parse().unwrap()
51    }
52}
53
54node! {
55    struct Unit
56}
57
58node! {
59    struct CodeBlock
60}
61
62impl<'a> CodeBlock<'a> {
63    pub fn statements(self) -> impl DoubleEndedIterator<Item = Statement<'a>> {
64        self.0.children().filter_map(SyntaxNode::cast)
65    }
66}
67
68node! {
69    struct Str
70}
71
72impl<'a> Str<'a> {
73    ///  Get the string value with resolved escape sequences
74    pub fn get(self) -> EcoString {
75        let text = self.0.text();
76        let unquoted = &text[1..text.len() - 1];
77        if !unquoted.contains('\\') {
78            return unquoted.into();
79        }
80
81        let mut out = EcoString::with_capacity(unquoted.len());
82        let mut s = Scanner::new(unquoted);
83
84        while let Some(c) = s.eat() {
85            if c != '\\' {
86                out.push(c);
87                continue;
88            }
89
90            let start = s.locate(-1);
91            match s.eat() {
92                Some('n') => out.push('\n'),
93                Some('r') => out.push('\r'),
94                Some('"') => out.push('"'),
95                Some('t') => out.push('\t'),
96                Some('\\') => out.push('\\'),
97                Some('u') if s.eat_if('{') => {
98                    let sequence = s.eat_while(char::is_ascii_hexdigit);
99                    s.eat_if('}');
100
101                    match u32::from_str_radix(sequence, 16)
102                        .ok()
103                        .and_then(std::char::from_u32)
104                    {
105                        Some(c) => out.push(c),
106                        None => out.push_str(s.from(start)),
107                    }
108                }
109                _ => out.push_str(s.from(start)),
110            }
111        }
112
113        out
114    }
115}
116
117node! {
118    struct Array
119}
120
121impl<'a> Array<'a> {
122    pub fn elements(self) -> impl DoubleEndedIterator<Item = Expr<'a>> {
123        self.0.children().filter_map(SyntaxNode::cast)
124    }
125}
126
127#[cfg(test)]
128mod tests {
129    use super::*;
130    use crate::assert_ast;
131
132    #[test]
133    fn parse_ident() {
134        assert_ast!(
135            "foo",
136            ident as Ident {
137                assert_eq!(ident.get(), "foo");
138            }
139        )
140    }
141
142    #[test]
143    fn parse_bool() {
144        assert_ast!(
145            "true",
146            bool as Bool {
147                assert_eq!(bool.get(), true);
148            }
149        )
150    }
151
152    #[test]
153    fn parse_int() {
154        assert_ast!(
155            "123",
156            int as Int {
157                assert_eq!(int.get(), 123);
158            }
159        )
160    }
161
162    #[test]
163    fn parse_str() {
164        assert_ast!(
165            "\"foo\"",
166            str as Str {
167                assert_eq!(str.get(), "foo");
168            }
169        )
170    }
171
172    #[test]
173    fn parse_array() {
174        assert_ast!(
175            "[1, 2, 3]",
176            array as Array {
177                array.elements() => [
178                    int as Int { assert_eq!(int.get(), 1); }
179                    int as Int { assert_eq!(int.get(), 2); }
180                    int as Int { assert_eq!(int.get(), 3); }
181                ]
182            }
183        )
184    }
185
186    #[test]
187    fn parse_code_block() {
188        assert_ast!(
189            "{ 1; 2; 3; }",
190            code_block as CodeBlock {
191                code_block.statements() => [
192                    int as Int { assert_eq!(int.get(), 1); }
193                    int as Int { assert_eq!(int.get(), 2); }
194                    int as Int { assert_eq!(int.get(), 3); }
195                ]
196            }
197        )
198    }
199
200    #[test]
201    fn parse_unit() {
202        assert_ast!(
203            "()",
204            _unit as Unit {}
205        )
206    }
207}