cairo_lang_parser/
macro_helpers.rs

1use cairo_lang_diagnostics::DiagnosticsBuilder;
2use cairo_lang_filesystem::db::FilesGroup;
3use cairo_lang_filesystem::ids::FileId;
4use cairo_lang_filesystem::span::TextSpan;
5use cairo_lang_syntax::node::ast::{
6    self, AttributeListGreen, ExprInlineMacro, ExprPathGreen, ItemInlineMacro,
7    LegacyExprInlineMacro, LegacyItemInlineMacro, TerminalNotGreen, TerminalSemicolonGreen,
8    TokenTree, TokenTreeNode, WrappedArgListGreen,
9};
10use cairo_lang_syntax::node::kind::SyntaxKind;
11use cairo_lang_syntax::node::{SyntaxNode, TypedSyntaxNode};
12use salsa::Database;
13
14use crate::ParserDiagnostic;
15use crate::diagnostic::ParserDiagnosticKind;
16use crate::parser::{Parser, SkippedError};
17use crate::recovery::is_of_kind;
18
19/// Takes a token tree syntax node, which is assumed to be parsable as a wrapped argument list, try
20/// to parse it as such and return the result.
21pub fn token_tree_as_wrapped_arg_list<'a>(
22    token_tree: TokenTreeNode<'a>,
23    db: &'a dyn Database,
24) -> Option<WrappedArgListGreen<'a>> {
25    let mut diagnostics: DiagnosticsBuilder<'_, ParserDiagnostic<'a>> =
26        DiagnosticsBuilder::default();
27    let node_text = token_tree.as_syntax_node().get_text(db);
28    let file_id = token_tree.stable_ptr(db).0.file_id(db);
29    let mut parser = Parser::new(db, file_id, node_text, &mut diagnostics);
30    let wrapped_arg_list_green: WrappedArgListGreen<'a> = parser.parse_wrapped_arg_list();
31    if let Err(SkippedError(span)) = parser.skip_until(is_of_kind!()) {
32        parser.add_diagnostic(
33            ParserDiagnosticKind::SkippedElement { element_name: "end arg list".into() },
34            span,
35        );
36    };
37    let diagnostics = diagnostics.build();
38    if !diagnostics.get_all().is_empty() {
39        return None;
40    }
41    Some(wrapped_arg_list_green)
42}
43
44/// Takes a token tree syntax node, which is assumed to be parsable as an expression (it assumes
45/// that the prefix is an expr, not the whole iterator), tries to parse it as such, and returns the
46/// result. The token tree iterator is consumed entirely. The resulting expression's offset
47/// corresponds to the offset of the first token in the provided token tree.
48pub fn as_expr_macro_token_tree<'a>(
49    mut token_tree: impl DoubleEndedIterator<Item = TokenTree<'a>>,
50    file_id: FileId<'a>,
51    db: &'a dyn Database,
52) -> Option<ast::Expr<'a>> {
53    let mut diagnostics: DiagnosticsBuilder<'_, ParserDiagnostic<'_>> =
54        DiagnosticsBuilder::default();
55    let first_token = token_tree.next()?.as_syntax_node();
56    let last_token =
57        token_tree.next_back().map(|last| last.as_syntax_node()).unwrap_or(first_token);
58    let file_content = db.file_content(file_id).expect("Failed to read file content");
59
60    let span = TextSpan::new(first_token.offset(db), last_token.span(db).end);
61
62    let mut parser = Parser::new(db, file_id, span.take(file_content), &mut diagnostics);
63    let expr_green = parser.parse_expr();
64    let expr = ast::Expr::from_syntax_node(
65        db,
66        SyntaxNode::new_root_with_offset(db, file_id, expr_green.0, Some(first_token.offset(db))),
67    );
68    Some(expr)
69}
70
71/// Trait for converting inline macros with token tree syntax as the argument to legacy inline which
72/// must have a wrapped argument list syntax node.
73pub trait AsLegacyInlineMacro<'a> {
74    /// The corresponding legacy inline macro type.
75    type LegacyType;
76    /// Converts the inline macro to the legacy inline macro.
77    fn as_legacy_inline_macro(&self, db: &'a dyn Database) -> Option<Self::LegacyType>;
78}
79
80impl<'a> AsLegacyInlineMacro<'a> for ExprInlineMacro<'a> {
81    type LegacyType = LegacyExprInlineMacro<'a>;
82
83    fn as_legacy_inline_macro(&self, db: &'a dyn Database) -> Option<Self::LegacyType> {
84        let green_node = self.as_syntax_node().green_node(db);
85        let [macro_name, bang, _arguments] = green_node.children() else {
86            return None;
87        };
88        let macro_name = ExprPathGreen(*macro_name);
89        let bang = TerminalNotGreen(*bang);
90        let wrapped_arg_list = token_tree_as_wrapped_arg_list(self.arguments(db), db)?;
91        let legacy_green = LegacyExprInlineMacro::new_green(db, macro_name, bang, wrapped_arg_list);
92        let file_id = self.stable_ptr(db).0.file_id(db);
93        let offset = self.stable_ptr(db).0.lookup(db).offset(db);
94        Some(LegacyExprInlineMacro::from_syntax_node(
95            db,
96            SyntaxNode::new_root_with_offset(db, file_id, legacy_green.0, Some(offset)),
97        ))
98    }
99}
100
101impl<'a> AsLegacyInlineMacro<'a> for ItemInlineMacro<'a> {
102    type LegacyType = LegacyItemInlineMacro<'a>;
103
104    fn as_legacy_inline_macro(&self, db: &'a dyn Database) -> Option<Self::LegacyType> {
105        let green_node = self.as_syntax_node().green_node(db);
106        let [attributes, macro_name, bang, _arguments, semicolon] = green_node.children() else {
107            return None;
108        };
109        let attributes = AttributeListGreen(*attributes);
110        let macro_path = ExprPathGreen(*macro_name);
111        let bang = TerminalNotGreen(*bang);
112        let wrapped_arg_list = token_tree_as_wrapped_arg_list(self.arguments(db), db)?;
113        let semicolon = TerminalSemicolonGreen(*semicolon);
114        let legacy_green = LegacyItemInlineMacro::new_green(
115            db,
116            attributes,
117            macro_path,
118            bang,
119            wrapped_arg_list,
120            semicolon,
121        );
122        let file_id = self.stable_ptr(db).0.file_id(db);
123        let offset = self.stable_ptr(db).0.lookup(db).offset(db);
124        Some(LegacyItemInlineMacro::from_syntax_node(
125            db,
126            SyntaxNode::new_root_with_offset(db, file_id, legacy_green.0, Some(offset)),
127        ))
128    }
129}