bytebraise_syntax/syntax/ast/
nodes.rs

1use crate::ast_node;
2use crate::syntax::ast;
3use crate::syntax::ast::quoted_value::QuotedValue;
4use crate::syntax::ast::tokens::{
5    AssignmentOperator, DirectiveArgument, Identifier, PythonDefFunctionName, Varflag,
6};
7use crate::syntax::ast::{AstChildren, AstNode, AstToken, SyntaxKind, SyntaxNode, support, tokens};
8
9ast_node!(Assignment, AssignmentNode);
10impl Assignment {
11    pub fn left(&self) -> IdentifierExpression {
12        support::child(&self.syntax).unwrap()
13    }
14
15    pub fn right(&self) -> QuotedValue {
16        support::token(&self.syntax).unwrap()
17    }
18
19    pub fn op(&self) -> AssignmentOperator {
20        support::token(&self.syntax).unwrap()
21    }
22}
23
24ast_node!(Export, ExportNode);
25impl Export {
26    pub fn export_kw(&self) -> tokens::Export {
27        support::token(&self.syntax).unwrap()
28    }
29
30    pub fn var(&self) -> IdentifierExpression {
31        support::child(&self.syntax).unwrap()
32    }
33
34    pub fn assignment(&self) -> Option<Assignment> {
35        support::child(&self.syntax)
36    }
37}
38
39ast_node!(Include, IncludeNode);
40impl Include {
41    pub fn value(&self) -> tokens::UnquotedValue {
42        support::token(&self.syntax).unwrap()
43    }
44}
45
46ast_node!(Require, RequireNode);
47impl Require {
48    pub fn value(&self) -> tokens::UnquotedValue {
49        support::token(&self.syntax).unwrap()
50    }
51}
52
53ast_node!(Unset, UnsetNode);
54ast_node!(ExportFunctions, ExportFunctionsNode);
55ast_node!(Inherit, InheritNode);
56impl Inherit {
57    pub fn value(&self) -> tokens::UnquotedValue {
58        support::token(&self.syntax).unwrap()
59    }
60}
61
62ast_node!(PythonDef, PythonDefNode);
63impl PythonDef {
64    /// Warning: may contain trailing spaces!
65    pub fn function_name(&self) -> PythonDefFunctionName {
66        support::token(&self.syntax).unwrap()
67    }
68}
69
70ast_node!(AddTask, AddTaskNode);
71impl AddTask {
72    pub fn task_name(&self) -> DirectiveArgument {
73        self.syntax
74            .children_with_tokens()
75            .filter_map(|it| it.into_token())
76            // only consider tokens up until we encounter 'after' or 'before'
77            .take_while(|it| !matches!(it.kind(), SyntaxKind::After | SyntaxKind::Before))
78            .find_map(DirectiveArgument::cast)
79            .unwrap()
80    }
81
82    pub fn after(&self) -> Vec<DirectiveArgument> {
83        self.syntax
84            .children_with_tokens()
85            .filter_map(|it| it.into_token())
86            .skip_while(|it| !matches!(it.kind(), SyntaxKind::After))
87            .take_while(|it| !matches!(it.kind(), SyntaxKind::Before))
88            .filter_map(DirectiveArgument::cast)
89            .collect()
90    }
91
92    pub fn after_names(&self) -> Vec<String> {
93        self.after()
94            .into_iter()
95            .map(|t| t.syntax.text().to_string())
96            .collect()
97    }
98
99    pub fn before(&self) -> Vec<DirectiveArgument> {
100        self.syntax
101            .children_with_tokens()
102            .filter_map(|it| it.into_token())
103            .skip_while(|it| !matches!(it.kind(), SyntaxKind::Before))
104            .take_while(|it| !matches!(it.kind(), SyntaxKind::After))
105            .filter_map(DirectiveArgument::cast)
106            .collect()
107    }
108
109    pub fn before_names(&self) -> Vec<String> {
110        self.before()
111            .into_iter()
112            .map(|t| t.syntax.text().to_string())
113            .collect()
114    }
115}
116
117ast_node!(DelTask, DelTaskNode);
118ast_node!(AddHandler, AddHandlerNode);
119ast_node!(Comment, Comment);
120
121ast_node!(IdentifierExpression, IdentifierExpressionNode);
122impl IdentifierExpression {
123    pub fn identifier(&self) -> Identifier {
124        support::token(&self.syntax).unwrap()
125    }
126
127    pub fn varflag(&self) -> Option<Varflag> {
128        support::token(&self.syntax)
129    }
130}
131
132ast_node!(Root, RootNode);
133impl Root {
134    pub fn items(&self) -> AstChildren<RootItem> {
135        support::children(self.syntax())
136    }
137
138    pub fn tasks(&self) -> AstChildren<Task> {
139        support::children(self.syntax())
140    }
141
142    pub fn assignments(&self) -> AstChildren<Assignment> {
143        support::children(self.syntax())
144    }
145
146    // TODO: handle returning override variants?
147    pub fn identifier_assignments<'a, 'b: 'a>(
148        &'a self,
149        identifier: &'b str,
150    ) -> IdentifierAssignments<'a> {
151        IdentifierAssignments {
152            inner: self.assignments(),
153            identifier,
154        }
155    }
156}
157
158#[derive(Debug, Clone)]
159pub struct IdentifierAssignments<'a> {
160    inner: AstChildren<Assignment>,
161    identifier: &'a str,
162}
163
164impl<'a> Iterator for IdentifierAssignments<'a> {
165    type Item = Assignment;
166
167    fn next(&mut self) -> Option<Self::Item> {
168        let identifier = self.identifier;
169        self.inner.find(|i| i.left().syntax.text() == identifier)
170    }
171}
172
173#[derive(Debug, Clone, PartialEq, Eq, Hash)]
174pub enum RootItem {
175    Task(Task),
176    Comment(Comment),
177    Directive(Directive),
178    PythonDef(PythonDef),
179    Assignment(Assignment),
180}
181
182impl AstNode for RootItem {
183    fn can_cast(kind: SyntaxKind) -> bool
184    where
185        Self: Sized,
186    {
187        match kind {
188            SyntaxKind::TaskNode
189            | SyntaxKind::Comment
190            | SyntaxKind::PythonDefNode
191            | SyntaxKind::AssignmentNode => true,
192            k if Directive::can_cast(k) => true,
193            _ => false,
194        }
195    }
196
197    fn cast(syntax: SyntaxNode) -> Option<Self>
198    where
199        Self: Sized,
200    {
201        let res = match syntax.kind() {
202            SyntaxKind::TaskNode => RootItem::Task(Task { syntax }),
203            SyntaxKind::Comment => RootItem::Comment(Comment { syntax }),
204            SyntaxKind::PythonDefNode => RootItem::PythonDef(PythonDef { syntax }),
205            SyntaxKind::AssignmentNode => RootItem::Assignment(Assignment { syntax }),
206            k if Directive::can_cast(k) => RootItem::Directive(Directive::cast(syntax).unwrap()),
207            _ => return None,
208        };
209
210        Some(res)
211    }
212
213    fn syntax(&self) -> &SyntaxNode {
214        match self {
215            RootItem::Task(it) => &it.syntax,
216            RootItem::Comment(it) => &it.syntax,
217            RootItem::Directive(it) => it.syntax(),
218            RootItem::PythonDef(it) => &it.syntax,
219            RootItem::Assignment(it) => &it.syntax,
220        }
221    }
222}
223
224#[derive(Debug, Clone, PartialEq, Eq, Hash)]
225pub enum Directive {
226    AddHandler(AddHandler),
227    AddTask(AddTask),
228    DelTask(DelTask),
229    Unset(Unset),
230    Inherit(Inherit),
231    Include(Include),
232    Require(Require),
233    Export(Export),
234    ExportFunctions(ExportFunctions),
235}
236
237impl AstNode for Directive {
238    fn can_cast(kind: SyntaxKind) -> bool
239    where
240        Self: Sized,
241    {
242        use SyntaxKind::*;
243        match kind {
244            AddHandlerNode | AddTaskNode | DelTaskNode | UnsetNode | InheritNode | IncludeNode
245            | RequireNode | ExportNode | ExportFunctionsNode => true,
246            _ => false,
247        }
248    }
249
250    fn cast(syntax: SyntaxNode) -> Option<Self>
251    where
252        Self: Sized,
253    {
254        let res = match syntax.kind() {
255            SyntaxKind::AddHandlerNode => Directive::AddHandler(AddHandler { syntax }),
256            SyntaxKind::AddTaskNode => Directive::AddTask(AddTask { syntax }),
257            SyntaxKind::DelTaskNode => Directive::DelTask(DelTask { syntax }),
258            SyntaxKind::UnsetNode => Directive::Unset(Unset { syntax }),
259            SyntaxKind::InheritNode => Directive::Inherit(Inherit { syntax }),
260            SyntaxKind::IncludeNode => Directive::Include(Include { syntax }),
261            SyntaxKind::RequireNode => Directive::Require(Require { syntax }),
262            SyntaxKind::ExportNode => Directive::Export(Export { syntax }),
263            SyntaxKind::ExportFunctionsNode => {
264                Directive::ExportFunctions(ExportFunctions { syntax })
265            }
266            _ => return None,
267        };
268
269        Some(res)
270    }
271
272    fn syntax(&self) -> &SyntaxNode {
273        match self {
274            Directive::AddHandler(it) => &it.syntax,
275            Directive::AddTask(it) => &it.syntax,
276            Directive::DelTask(it) => &it.syntax,
277            Directive::Unset(it) => &it.syntax,
278            Directive::Inherit(it) => &it.syntax,
279            Directive::Include(it) => &it.syntax,
280            Directive::Require(it) => &it.syntax,
281            Directive::Export(it) => &it.syntax,
282            Directive::ExportFunctions(it) => &it.syntax,
283        }
284    }
285}
286
287ast_node!(Task, TaskNode);
288impl Task {
289    pub fn name(&self) -> Option<Identifier> {
290        let id_node: Option<IdentifierExpression> = support::child(self.syntax());
291        id_node.map(|id| id.identifier())
292    }
293
294    pub fn body(&self) -> tokens::Task {
295        support::token(&self.syntax).unwrap()
296    }
297
298    pub fn name_or_anonymous(&self) -> String {
299        self.name()
300            .as_ref()
301            .map(|n| n.text().to_string())
302            .unwrap_or(String::from("__anonymous"))
303    }
304
305    pub fn is_python(&self) -> bool {
306        self.syntax
307            .children_with_tokens()
308            .filter_map(|it| it.into_token())
309            .find_map(ast::tokens::Python::cast)
310            .is_some()
311    }
312
313    pub fn is_fakeroot(&self) -> bool {
314        self.syntax
315            .children_with_tokens()
316            .filter_map(|it| it.into_token())
317            .find_map(ast::tokens::Fakeroot::cast)
318            .is_some()
319    }
320
321    pub fn is_anonymous_python(&self) -> bool {
322        self.is_python()
323            && matches!(
324                self.name().as_ref().map(|n| n.text()),
325                Some("__anonymous") | None
326            )
327    }
328}