rnix/ast/
tokens.rs

1use core::num;
2
3use crate::{
4    ast::AstToken,
5    SyntaxKind::{self, *},
6    SyntaxToken,
7};
8
9macro_rules! token {
10    (
11        #[from($kind:ident)]
12        $(#[$meta:meta])*
13        struct $name:ident;
14    ) => {
15        #[derive(Debug, Clone, PartialEq, Eq, Hash)]
16        $(#[$meta])*
17        pub struct $name(pub(super) SyntaxToken);
18
19        impl std::fmt::Display for $name {
20            fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
21                std::fmt::Display::fmt(self.syntax(), f)
22            }
23        }
24
25        impl AstToken for $name {
26            fn can_cast(kind: SyntaxKind) -> bool {
27                $kind == kind
28            }
29
30            fn cast(from: SyntaxToken) -> Option<Self> {
31                if from.kind() == $kind {
32                    Some(Self(from))
33                } else {
34                    None
35                }
36            }
37
38            fn syntax(&self) -> &SyntaxToken {
39                &self.0
40            }
41        }
42    };
43}
44
45token! { #[from(TOKEN_WHITESPACE)] struct Whitespace; }
46
47token! { #[from(TOKEN_COMMENT)] struct Comment; }
48
49impl Comment {
50    pub fn text(&self) -> &str {
51        let text = self.syntax().text();
52        // Handle both "#..." and "/*...*/" comments.
53        match text.strip_prefix("#") {
54            Some(s) => s,
55            None => text.strip_prefix(r#"/*"#).unwrap().strip_suffix(r#"*/"#).unwrap(),
56        }
57    }
58}
59
60#[cfg(test)]
61mod tests {
62    use crate::ast;
63    use crate::match_ast;
64    use crate::Root;
65
66    use super::*;
67    use rowan::ast::AstNode;
68
69    #[test]
70    fn comment() {
71        let s = "# comment bruh
72/* this is a multiline comment wow
73asdfasdf
74asdfasdf */
751 + 1
76/* last one */
77";
78        let comments: Vec<String> = Root::parse(s)
79            .ok()
80            .unwrap()
81            .syntax()
82            .children_with_tokens()
83            .filter_map(|e| match e {
84                rowan::NodeOrToken::Token(token) => match_ast! { match token {
85                    ast::Comment(it) => Some(it.text().to_string()),
86                    _ => None,
87                }},
88                rowan::NodeOrToken::Node(_) => None,
89            })
90            .collect();
91        let expected = vec![
92            " comment bruh",
93            " this is a multiline comment wow\nasdfasdf\nasdfasdf ",
94            " last one ",
95        ];
96
97        assert_eq!(comments, expected);
98    }
99}
100
101token! { #[from(TOKEN_FLOAT)] struct Float; }
102
103impl Float {
104    pub fn value(&self) -> Result<f64, num::ParseFloatError> {
105        self.syntax().text().parse()
106    }
107}
108
109token! { #[from(TOKEN_INTEGER)] struct Integer; }
110
111impl Integer {
112    pub fn value(&self) -> Result<i64, num::ParseIntError> {
113        self.syntax().text().parse()
114    }
115}
116
117token! { #[from(TOKEN_PATH)] struct PathContent; }
118
119token! { #[from(TOKEN_STRING_CONTENT)] struct StrContent; }
120
121token! { #[from(TOKEN_URI)] struct Uri; }