rune/ast/
file.rs

1use crate::ast::prelude::*;
2
3#[test]
4#[cfg(not(miri))]
5fn ast_parse() {
6    rt::<ast::File>(
7        r#"
8        use foo;
9        ///
10        fn foo() {
11            42
12        }
13        ///
14        use bar;
15        ///
16        fn bar(a, b) {
17            a
18        }
19        "#,
20    );
21
22    rt::<ast::File>(
23        r#"
24        use http;
25
26        fn main() {
27            let client = http::client();
28            let response = client.get("https://google.com");
29            let text = response.text();
30        }
31        "#,
32    );
33
34    rt::<ast::File>(
35        r#"
36        // NB: Attributes are currently rejected by the compiler
37        #![feature(attributes)]
38
39        fn main() {}
40        "#,
41    );
42
43    let file = rt_with::<ast::File>(
44        r#"#!rune run
45
46        fn main() {}
47        "#,
48        true,
49    );
50
51    assert!(file.shebang.is_some());
52}
53
54/// A rune file.
55#[derive(Debug, TryClone, PartialEq, Eq, ToTokens, OptionSpanned)]
56#[non_exhaustive]
57pub struct File {
58    /// Top-level shebang.
59    #[rune(iter)]
60    pub shebang: Option<Shebang>,
61    /// Top level "Outer" `#![...]` attributes for the file
62    #[rune(iter)]
63    pub attributes: Vec<ast::Attribute>,
64    /// All the declarations in a file.
65    #[rune(iter)]
66    pub items: Vec<(ast::Item, Option<T![;]>)>,
67}
68
69impl Parse for File {
70    fn parse(p: &mut Parser<'_>) -> Result<Self> {
71        let shebang = p.parse()?;
72
73        let mut attributes = try_vec![];
74
75        // only allow outer attributes at the top of a file
76        while p.peek::<ast::attribute::OuterAttribute>()? {
77            attributes.try_push(p.parse()?)?;
78        }
79
80        let mut items = Vec::new();
81
82        let mut item_attributes = p.parse()?;
83        let mut item_visibility = p.parse()?;
84        let mut path = p.parse::<Option<ast::Path>>()?;
85
86        while path.is_some() || ast::Item::peek_as_item(p.peeker()) {
87            let item: ast::Item =
88                ast::Item::parse_with_meta_path(p, item_attributes, item_visibility, path.take())?;
89
90            let semi_colon = if item.needs_semi_colon() || p.peek::<T![;]>()? {
91                Some(p.parse::<T![;]>()?)
92            } else {
93                None
94            };
95
96            items.try_push((item, semi_colon))?;
97            item_attributes = p.parse()?;
98            item_visibility = p.parse()?;
99            path = p.parse()?;
100        }
101
102        // meta without items. maybe use different error kind?
103        if let Some(span) = item_attributes.option_span() {
104            return Err(compile::Error::unsupported(span, "attributes"));
105        }
106
107        if let Some(span) = item_visibility.option_span() {
108            return Err(compile::Error::unsupported(span, "visibility"));
109        }
110
111        Ok(Self {
112            shebang,
113            attributes,
114            items,
115        })
116    }
117}
118
119/// The shebang of a file.
120#[derive(Debug, TryClone, PartialEq, Eq)]
121#[non_exhaustive]
122pub struct Shebang {
123    /// The span of the shebang.
124    pub span: Span,
125    /// The source of the shebang.
126    pub source: ast::LitSource,
127}
128
129impl Peek for Shebang {
130    fn peek(p: &mut Peeker<'_>) -> bool {
131        matches!(p.nth(0), K![#!(..)])
132    }
133}
134
135impl Parse for Shebang {
136    fn parse(p: &mut Parser<'_>) -> Result<Self> {
137        let token = p.next()?;
138
139        match token.kind {
140            K![#!(source)] => Ok(Self {
141                span: token.span,
142                source,
143            }),
144            _ => Err(compile::Error::expected(token, Expectation::Shebang)),
145        }
146    }
147}
148
149impl Spanned for Shebang {
150    fn span(&self) -> Span {
151        self.span
152    }
153}
154
155impl ToTokens for Shebang {
156    fn to_tokens(
157        &self,
158        _: &mut MacroContext<'_, '_, '_>,
159        stream: &mut TokenStream,
160    ) -> alloc::Result<()> {
161        stream.push(ast::Token {
162            span: self.span,
163            kind: ast::Kind::Shebang(self.source),
164        })
165    }
166}