1use std::sync::Arc;
2
3use cairo_lang_defs::ids::{
4 LanguageElementId, LookupItemId, MacroDeclarationId, ModuleFileId, ModuleItemId,
5};
6use cairo_lang_diagnostics::{Diagnostics, Maybe, ToMaybe, skip_diagnostic};
7use cairo_lang_filesystem::ids::{CodeMapping, CodeOrigin};
8use cairo_lang_filesystem::span::{TextSpan, TextWidth};
9use cairo_lang_parser::macro_helpers::as_expr_macro_token_tree;
10use cairo_lang_syntax::attribute::structured::{Attribute, AttributeListStructurize};
11use cairo_lang_syntax::node::ast::{MacroElement, MacroParam};
12use cairo_lang_syntax::node::db::SyntaxGroup;
13use cairo_lang_syntax::node::ids::SyntaxStablePtrId;
14use cairo_lang_syntax::node::kind::SyntaxKind;
15use cairo_lang_syntax::node::{SyntaxNode, Terminal, TypedStablePtr, TypedSyntaxNode, ast};
16use cairo_lang_utils::ordered_hash_map::OrderedHashMap;
17use cairo_lang_utils::ordered_hash_set::OrderedHashSet;
18
19use crate::SemanticDiagnostic;
20use crate::db::SemanticGroup;
21use crate::diagnostic::{SemanticDiagnosticKind, SemanticDiagnostics, SemanticDiagnosticsBuilder};
22use crate::expr::inference::InferenceId;
23use crate::keyword::{MACRO_CALL_SITE, MACRO_DEF_SITE};
24use crate::resolve::{Resolver, ResolverData};
25
26#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
29pub struct RepetitionId(usize);
30
31type Captures = OrderedHashMap<String, Vec<CapturedValue>>;
34
35#[derive(Default, Clone, Debug)]
38pub struct MatcherContext {
39 pub captures: Captures,
42
43 pub placeholder_to_rep_id: OrderedHashMap<String, RepetitionId>,
46
47 pub current_repetition_stack: Vec<RepetitionId>,
50
51 pub next_repetition_id: usize,
53
54 pub repetition_indices: OrderedHashMap<RepetitionId, usize>,
56
57 pub repetition_match_counts: OrderedHashMap<RepetitionId, usize>,
59
60 pub repetition_operators: OrderedHashMap<RepetitionId, ast::MacroRepetitionOperator>,
62}
63
64#[derive(Debug, Clone, PartialEq, Eq)]
66pub struct MacroDeclarationData {
67 rules: Vec<MacroRuleData>,
68 attributes: Vec<Attribute>,
69 diagnostics: Diagnostics<SemanticDiagnostic>,
70 resolver_data: Arc<ResolverData>,
71}
72
73#[derive(Debug, Clone, PartialEq, Eq)]
75pub struct MacroRuleData {
76 pub pattern: ast::WrappedMacro,
77 pub expansion: ast::MacroElements,
78}
79
80#[derive(Debug, Clone, PartialEq, Eq)]
82enum PlaceholderKind {
83 Identifier,
84 Expr,
85}
86
87impl From<ast::MacroParamKind> for PlaceholderKind {
88 fn from(kind: ast::MacroParamKind) -> Self {
89 match kind {
90 ast::MacroParamKind::Identifier(_) => PlaceholderKind::Identifier,
91 ast::MacroParamKind::Expr(_) => PlaceholderKind::Expr,
92 ast::MacroParamKind::Missing(_) => unreachable!(
93 "Missing macro rule param kind, should have been handled by the parser."
94 ),
95 }
96 }
97}
98
99#[derive(Clone, Debug, PartialEq, Eq)]
101pub struct CapturedValue {
102 pub text: String,
103 pub stable_ptr: SyntaxStablePtrId,
104}
105
106pub fn priv_macro_declaration_data(
108 db: &dyn SemanticGroup,
109 macro_declaration_id: MacroDeclarationId,
110) -> Maybe<MacroDeclarationData> {
111 let mut diagnostics = SemanticDiagnostics::default();
112
113 let module_file_id = macro_declaration_id.module_file_id(db);
114 let macro_declaration_syntax =
115 db.module_macro_declaration_by_id(macro_declaration_id)?.to_maybe()?;
116 if !are_user_defined_inline_macros_enabled(db, module_file_id) {
117 diagnostics.report(
118 macro_declaration_syntax.stable_ptr(db).untyped(),
119 SemanticDiagnosticKind::UserDefinedInlineMacrosDisabled,
120 );
121 }
122
123 let attributes = macro_declaration_syntax.attributes(db).structurize(db);
124 let inference_id = InferenceId::LookupItemDeclaration(LookupItemId::ModuleItem(
125 ModuleItemId::MacroDeclaration(macro_declaration_id),
126 ));
127 let resolver = Resolver::new(db, module_file_id, inference_id);
128
129 let mut rules = vec![];
132 for rule_syntax in macro_declaration_syntax.rules(db).elements(db) {
133 let pattern = rule_syntax.lhs(db);
134 let expansion = rule_syntax.rhs(db).elements(db);
135 let pattern_elements = get_macro_elements(db, pattern.clone());
136 let defined_placeholders =
138 OrderedHashSet::<_>::from_iter(pattern_elements.elements(db).filter_map(|element| {
139 match element {
140 ast::MacroElement::Param(param) => {
141 Some(param.name(db).as_syntax_node().get_text_without_trivia(db))
142 }
143 ast::MacroElement::Repetition(repetition) => repetition
144 .elements(db)
145 .elements(db)
146 .filter_map(|inner_element| match inner_element {
147 ast::MacroElement::Param(inner_param) => Some(
148 inner_param.name(db).as_syntax_node().get_text_without_trivia(db),
149 ),
150 _ => None,
151 })
152 .collect::<Vec<_>>()
153 .into_iter()
154 .next(),
155 _ => None,
156 }
157 }));
158
159 let used_placeholders = collect_expansion_placeholders(db, expansion.as_syntax_node());
160 for (placeholder_ptr, used_placeholder) in used_placeholders {
162 if !defined_placeholders.contains(&used_placeholder) {
163 diagnostics.report(
164 placeholder_ptr,
165 SemanticDiagnosticKind::UndefinedMacroPlaceholder(used_placeholder),
166 );
167 }
168 }
169 rules.push(MacroRuleData { pattern, expansion });
170 }
171 let resolver_data = Arc::new(resolver.data);
172 Ok(MacroDeclarationData { diagnostics: diagnostics.build(), attributes, resolver_data, rules })
173}
174
175fn get_macro_elements(db: &dyn SyntaxGroup, pattern: ast::WrappedMacro) -> ast::MacroElements {
177 match pattern {
178 ast::WrappedMacro::Parenthesized(inner) => inner.elements(db),
179 ast::WrappedMacro::Braced(inner) => inner.elements(db),
180 ast::WrappedMacro::Bracketed(inner) => inner.elements(db),
181 }
182}
183
184fn extract_placeholder(db: &dyn SyntaxGroup, path_node: &MacroParam) -> Option<String> {
187 let placeholder_name = path_node.name(db).as_syntax_node().get_text_without_trivia(db);
188 if ![MACRO_DEF_SITE, MACRO_CALL_SITE].contains(&placeholder_name.as_str()) {
189 return Some(placeholder_name);
190 }
191 None
192}
193
194fn collect_expansion_placeholders(
196 db: &dyn SyntaxGroup,
197 node: SyntaxNode,
198) -> Vec<(SyntaxStablePtrId, String)> {
199 let mut placeholders = Vec::new();
200 if node.kind(db) == SyntaxKind::MacroParam {
201 let path_node = MacroParam::from_syntax_node(db, node);
202 if let Some(placeholder_name) = extract_placeholder(db, &path_node) {
203 placeholders.push((path_node.stable_ptr(db).untyped(), placeholder_name));
204 return placeholders;
205 }
206 }
207 if node.kind(db) == SyntaxKind::MacroRepetition {
208 let repetition = ast::MacroRepetition::from_syntax_node(db, node);
209 for element in repetition.elements(db).elements(db) {
210 placeholders.extend(collect_expansion_placeholders(db, element.as_syntax_node()));
211 }
212 return placeholders;
213 }
214 if !node.kind(db).is_terminal() {
215 for child in node.get_children(db).iter() {
216 placeholders.extend(collect_expansion_placeholders(db, *child));
217 }
218 }
219 placeholders
220}
221
222pub fn is_macro_rule_match(
225 db: &dyn SemanticGroup,
226 rule: &MacroRuleData,
227 input: &ast::TokenTreeNode,
228) -> Option<(Captures, OrderedHashMap<String, RepetitionId>)> {
229 let mut ctx = MatcherContext::default();
230
231 let matcher_elements = get_macro_elements(db, rule.pattern.clone());
232 let input_elements = match input.subtree(db) {
233 ast::WrappedTokenTree::Parenthesized(tt) => tt.tokens(db),
234 ast::WrappedTokenTree::Braced(tt) => tt.tokens(db),
235 ast::WrappedTokenTree::Bracketed(tt) => tt.tokens(db),
236 ast::WrappedTokenTree::Missing(_) => unreachable!(),
237 }
238 .elements_vec(db);
239 let mut input_iter = input_elements.iter().peekable();
240 is_macro_rule_match_ex(db, matcher_elements, &mut input_iter, &mut ctx, true)?;
241 if !validate_repetition_operator_constraints(&ctx) {
242 return None;
243 }
244 Some((ctx.captures, ctx.placeholder_to_rep_id))
245}
246
247fn is_macro_rule_match_ex(
251 db: &dyn SemanticGroup,
252 matcher_elements: ast::MacroElements,
253 input_iter: &mut std::iter::Peekable<std::slice::Iter<'_, ast::TokenTree>>,
254 ctx: &mut MatcherContext,
255 consume_all_input: bool,
256) -> Option<()> {
257 for matcher_element in matcher_elements.elements(db) {
258 match matcher_element {
259 ast::MacroElement::Token(matcher_token) => {
260 let input_token = input_iter.next()?;
261 match input_token {
262 ast::TokenTree::Token(token_tree_leaf) => {
263 if matcher_token.as_syntax_node().get_text_without_trivia(db)
264 != token_tree_leaf.as_syntax_node().get_text_without_trivia(db)
265 {
266 return None;
267 }
268 continue;
269 }
270 ast::TokenTree::Subtree(_) => return None,
271 ast::TokenTree::Repetition(_) => return None,
272 ast::TokenTree::Param(_) => return None,
273 ast::TokenTree::Missing(_) => unreachable!(),
274 }
275 }
276 ast::MacroElement::Param(param) => {
277 let placeholder_kind: PlaceholderKind =
278 if let ast::OptionParamKind::ParamKind(param_kind) = param.kind(db) {
279 param_kind.kind(db).into()
280 } else {
281 unreachable!(
282 "Missing macro rule param kind, should have been handled by the \
283 parser."
284 )
285 };
286 let placeholder_name = param.name(db).as_syntax_node().get_text_without_trivia(db);
287 match placeholder_kind {
288 PlaceholderKind::Identifier => {
289 let input_token = input_iter.next()?;
290 let captured_text = match input_token {
291 ast::TokenTree::Token(token_tree_leaf) => {
292 match token_tree_leaf.leaf(db) {
293 ast::TokenNode::TerminalIdentifier(terminal_identifier) => {
294 terminal_identifier.text(db).to_string()
295 }
296 _ => return None,
297 }
298 }
299 _ => return None,
300 };
301 ctx.captures.entry(placeholder_name.clone()).or_default().push(
302 CapturedValue {
303 text: captured_text,
304 stable_ptr: input_token.stable_ptr(db).untyped(),
305 },
306 );
307 if let Some(rep_id) = ctx.current_repetition_stack.last() {
308 ctx.placeholder_to_rep_id.insert(placeholder_name.clone(), *rep_id);
309 }
310 continue;
311 }
312 PlaceholderKind::Expr => {
313 let mut cloned_iter = input_iter.clone();
314 let peek_token = cloned_iter.peek()?;
315 let file_id = peek_token.as_syntax_node().stable_ptr(db).file_id(db);
316 let expr_node =
317 as_expr_macro_token_tree(input_iter.clone().cloned(), file_id, db)?;
318 let expr_text = expr_node.as_syntax_node().get_text(db);
319 let expr_length = expr_text.len();
320 if expr_length == 0 {
323 return None;
324 }
325
326 ctx.captures.entry(placeholder_name.clone()).or_default().push(
327 CapturedValue {
328 text: expr_text.to_string(),
329 stable_ptr: peek_token.stable_ptr(db).untyped(),
330 },
331 );
332 if let Some(rep_id) = ctx.current_repetition_stack.last() {
333 ctx.placeholder_to_rep_id.insert(placeholder_name.clone(), *rep_id);
334 }
335 let expr_length = expr_text.len();
336 let mut current_length = 0;
337
338 for token_tree_leaf in input_iter.by_ref() {
342 let token_text = match token_tree_leaf {
343 ast::TokenTree::Token(token_tree_leaf) => {
344 token_tree_leaf.as_syntax_node().get_text(db)
345 }
346 ast::TokenTree::Subtree(token_subtree) => {
347 token_subtree.as_syntax_node().get_text(db)
348 }
349 ast::TokenTree::Repetition(token_repetition) => {
350 token_repetition.as_syntax_node().get_text(db)
351 }
352 ast::TokenTree::Param(token_param) => {
353 token_param.as_syntax_node().get_text(db)
354 }
355 ast::TokenTree::Missing(_) => unreachable!(),
356 };
357 current_length += token_text.len();
358 if current_length >= expr_length {
359 break;
360 }
361 }
362 continue;
363 }
364 }
365 }
366 ast::MacroElement::Subtree(matcher_subtree) => {
367 let input_token = input_iter.next()?;
368 if let ast::TokenTree::Subtree(input_subtree) = input_token {
369 let inner_elements = get_macro_elements(db, matcher_subtree.subtree(db));
370 let inner_input_elements = match input_subtree.subtree(db) {
371 ast::WrappedTokenTree::Parenthesized(tt) => tt.tokens(db),
372 ast::WrappedTokenTree::Braced(tt) => tt.tokens(db),
373 ast::WrappedTokenTree::Bracketed(tt) => tt.tokens(db),
374 ast::WrappedTokenTree::Missing(_) => unreachable!(),
375 }
376 .elements_vec(db);
377 let mut inner_input_iter = inner_input_elements.iter().peekable();
378 is_macro_rule_match_ex(db, inner_elements, &mut inner_input_iter, ctx, true)?;
379 continue;
380 } else {
381 return None;
382 }
383 }
384 ast::MacroElement::Repetition(repetition) => {
385 let rep_id = RepetitionId(ctx.next_repetition_id);
386 ctx.next_repetition_id += 1;
387 ctx.current_repetition_stack.push(rep_id);
388 let elements = repetition.elements(db);
389 let operator = repetition.operator(db);
390 let separator_token = repetition.separator(db);
391 let expected_separator = match separator_token {
392 ast::OptionTerminalComma::TerminalComma(sep) => {
393 Some(sep.as_syntax_node().get_text_without_trivia(db))
394 }
395 ast::OptionTerminalComma::Empty(_) => None,
396 };
397 let mut match_count = 0;
398 loop {
399 let mut inner_ctx = ctx.clone();
400 let mut temp_iter = input_iter.clone();
401 if is_macro_rule_match_ex(
402 db,
403 elements.clone(),
404 &mut temp_iter,
405 &mut inner_ctx,
406 false,
407 )
408 .is_none()
409 {
410 break;
411 }
412 *ctx = inner_ctx;
413 *input_iter = temp_iter;
414 match_count += 1;
415 if let Some(expected_sep) = &expected_separator {
416 if let Some(ast::TokenTree::Token(token_leaf)) = input_iter.peek() {
417 let actual = token_leaf.as_syntax_node().get_text_without_trivia(db);
418 if actual == *expected_sep {
419 input_iter.next();
420 } else {
421 break;
422 }
423 } else {
424 break;
425 }
426 }
427 }
428 ctx.repetition_match_counts.insert(rep_id, match_count);
429 ctx.repetition_operators.insert(rep_id, operator.clone());
430 for (placeholder_name, _) in ctx.captures.clone() {
431 ctx.placeholder_to_rep_id.insert(placeholder_name.clone(), rep_id);
432 }
433
434 for i in 0..match_count {
435 ctx.repetition_indices.insert(rep_id, i);
436 }
437 ctx.current_repetition_stack.pop();
438 continue;
439 }
440 }
441 }
442
443 if consume_all_input && input_iter.next().is_some() {
444 return None;
445 }
446 Some(())
447}
448
449fn validate_repetition_operator_constraints(ctx: &MatcherContext) -> bool {
450 for (rep_id, count) in ctx.repetition_match_counts.clone() {
451 match ctx.repetition_operators.get(&rep_id) {
452 Some(ast::MacroRepetitionOperator::ZeroOrOne(_)) if count > 1 => return false,
453 Some(ast::MacroRepetitionOperator::OneOrMore(_)) if count < 1 => return false,
454 Some(ast::MacroRepetitionOperator::ZeroOrMore(_)) | None => {}
455 _ => {}
456 }
457 }
458 true
459}
460
461#[derive(Debug, Clone, PartialEq, Eq)]
463pub struct MacroExpansionResult {
464 pub text: Arc<str>,
466 pub code_mappings: Arc<[CodeMapping]>,
468}
469
470pub fn expand_macro_rule(
476 db: &dyn SyntaxGroup,
477 rule: &MacroRuleData,
478 matcher_ctx: &mut MatcherContext,
479) -> Maybe<MacroExpansionResult> {
480 let node = rule.expansion.as_syntax_node();
481 let mut res_buffer = String::new();
482 let mut code_mappings = Vec::new();
483 expand_macro_rule_ex(db, node, matcher_ctx, &mut res_buffer, &mut code_mappings)?;
484 Ok(MacroExpansionResult { text: res_buffer.into(), code_mappings: code_mappings.into() })
485}
486
487fn expand_macro_rule_ex(
493 db: &dyn SyntaxGroup,
494 node: SyntaxNode,
495 matcher_ctx: &mut MatcherContext,
496 res_buffer: &mut String,
497 code_mappings: &mut Vec<CodeMapping>,
498) -> Maybe<()> {
499 match node.kind(db) {
500 SyntaxKind::MacroParam => {
501 let path_node = MacroParam::from_syntax_node(db, node);
502 if let Some(name) = extract_placeholder(db, &path_node) {
503 let rep_index = matcher_ctx
504 .placeholder_to_rep_id
505 .get(&name)
506 .and_then(|rep_id| matcher_ctx.repetition_indices.get(rep_id))
507 .copied();
508 let value = matcher_ctx
509 .captures
510 .get(&name)
511 .and_then(|v| rep_index.map_or_else(|| v.first(), |i| v.get(i)))
512 .ok_or_else(skip_diagnostic)?;
513 let start = TextWidth::from_str(res_buffer).as_offset();
514 let span =
515 TextSpan { start, end: start.add_width(TextWidth::from_str(&value.text)) };
516 res_buffer.push_str(&value.text);
517 code_mappings.push(CodeMapping {
518 span,
519 origin: CodeOrigin::Span(value.stable_ptr.lookup(db).span_without_trivia(db)),
520 });
521 return Ok(());
522 }
523 }
524 SyntaxKind::MacroRepetition => {
525 let repetition = ast::MacroRepetition::from_syntax_node(db, node);
526 let elements = repetition.elements(db).elements_vec(db);
527 let repetition_params = get_repetition_params(db, elements.iter().cloned());
528 let first_param = repetition_params.first().ok_or_else(skip_diagnostic)?;
529 let placeholder_name = first_param.name(db).text(db).to_string();
530 let rep_id = *matcher_ctx
531 .placeholder_to_rep_id
532 .get(&placeholder_name)
533 .ok_or_else(skip_diagnostic)?;
534 let repetition_len =
535 matcher_ctx.captures.get(&placeholder_name).map(|v| v.len()).unwrap_or(0);
536 for i in 0..repetition_len {
537 matcher_ctx.repetition_indices.insert(rep_id, i);
538 for element in &elements {
539 expand_macro_rule_ex(
540 db,
541 element.as_syntax_node(),
542 matcher_ctx,
543 res_buffer,
544 code_mappings,
545 )?;
546 }
547
548 if i + 1 < repetition_len {
549 if let ast::OptionTerminalComma::TerminalComma(sep) = repetition.separator(db) {
550 res_buffer.push_str(&sep.as_syntax_node().get_text(db));
551 }
552 }
553 }
554
555 matcher_ctx.repetition_indices.swap_remove(&rep_id);
556 return Ok(());
557 }
558 _ => {
559 if node.kind(db).is_terminal() {
560 res_buffer.push_str(&node.get_text(db));
561 return Ok(());
562 }
563
564 for child in node.get_children(db).iter() {
565 expand_macro_rule_ex(db, *child, matcher_ctx, res_buffer, code_mappings)?;
566 }
567 return Ok(());
568 }
569 }
570 if node.kind(db).is_terminal() {
571 res_buffer.push_str(&node.get_text(db));
572 return Ok(());
573 }
574 for child in node.get_children(db).iter() {
575 expand_macro_rule_ex(db, *child, matcher_ctx, res_buffer, code_mappings)?;
576 }
577 Ok(())
578}
579
580fn get_repetition_params(
582 db: &dyn SyntaxGroup,
583 elements: impl IntoIterator<Item = MacroElement>,
584) -> Vec<MacroParam> {
585 let mut params = vec![];
586 repetition_params_extend(db, elements, &mut params);
587 params
588}
589
590fn repetition_params_extend(
592 db: &dyn SyntaxGroup,
593 elements: impl IntoIterator<Item = MacroElement>,
594 params: &mut Vec<MacroParam>,
595) {
596 for element in elements {
597 match element {
598 ast::MacroElement::Param(param) => {
599 params.push(param);
600 }
601 ast::MacroElement::Subtree(subtree) => {
602 let inner_elements = get_macro_elements(db, subtree.subtree(db)).elements(db);
603 repetition_params_extend(db, inner_elements, params);
604 }
605 ast::MacroElement::Repetition(repetition) => {
606 let inner_elements = repetition.elements(db).elements(db);
607 repetition_params_extend(db, inner_elements, params);
608 }
609 ast::MacroElement::Token(_) => {}
610 }
611 }
612}
613
614pub fn macro_declaration_diagnostics(
616 db: &dyn SemanticGroup,
617 macro_declaration_id: MacroDeclarationId,
618) -> Diagnostics<SemanticDiagnostic> {
619 priv_macro_declaration_data(db, macro_declaration_id)
620 .map(|data| data.diagnostics)
621 .unwrap_or_default()
622}
623
624pub fn macro_declaration_attributes(
626 db: &dyn SemanticGroup,
627 macro_declaration_id: MacroDeclarationId,
628) -> Maybe<Vec<Attribute>> {
629 priv_macro_declaration_data(db, macro_declaration_id).map(|data| data.attributes)
630}
631
632pub fn macro_declaration_resolver_data(
634 db: &dyn SemanticGroup,
635 macro_declaration_id: MacroDeclarationId,
636) -> Maybe<Arc<ResolverData>> {
637 priv_macro_declaration_data(db, macro_declaration_id).map(|data| data.resolver_data)
638}
639
640pub fn macro_declaration_rules(
642 db: &dyn SemanticGroup,
643 macro_declaration_id: MacroDeclarationId,
644) -> Maybe<Vec<MacroRuleData>> {
645 priv_macro_declaration_data(db, macro_declaration_id).map(|data| data.rules)
646}
647
648fn are_user_defined_inline_macros_enabled(
650 db: &dyn SemanticGroup,
651 module_file_id: ModuleFileId,
652) -> bool {
653 let owning_crate = module_file_id.0.owning_crate(db);
654 let Some(config) = db.crate_config(owning_crate) else { return false };
655 config.settings.experimental_features.user_defined_inline_macros
656}