1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
mod array;
mod assert;
mod consteval_int;
mod format;
mod panic;
mod print;
mod write;

use cairo_lang_defs::plugin::{InlinePluginResult, PluginDiagnostic, PluginSuite};
use cairo_lang_plugins::get_base_plugins;
use cairo_lang_syntax::node::ast::{self};
use cairo_lang_syntax::node::db::SyntaxGroup;
use cairo_lang_syntax::node::helpers::WrappedArgListHelper;
use cairo_lang_syntax::node::{SyntaxNode, TypedSyntaxNode};
use itertools::Itertools;

use self::assert::AssertMacro;
use self::format::FormatMacro;
use self::panic::PanicMacro;
use self::print::{PrintMacro, PrintlnMacro};
use self::write::{WriteMacro, WritelnMacro};
use super::inline_macros::array::ArrayMacro;
use super::inline_macros::consteval_int::ConstevalIntMacro;

/// Gets the default plugin suite to load into the Cairo compiler.
pub fn get_default_plugin_suite() -> PluginSuite {
    let mut suite =
        PluginSuite { plugins: get_base_plugins(), inline_macro_plugins: Default::default() };
    suite
        .add_inline_macro_plugin::<ArrayMacro>()
        .add_inline_macro_plugin::<AssertMacro>()
        .add_inline_macro_plugin::<ConstevalIntMacro>()
        .add_inline_macro_plugin::<FormatMacro>()
        .add_inline_macro_plugin::<PanicMacro>()
        .add_inline_macro_plugin::<PrintMacro>()
        .add_inline_macro_plugin::<PrintlnMacro>()
        .add_inline_macro_plugin::<WriteMacro>()
        .add_inline_macro_plugin::<WritelnMacro>();
    suite
}

pub fn unsupported_bracket_diagnostic(
    db: &dyn SyntaxGroup,
    macro_ast: &ast::ExprInlineMacro,
) -> InlinePluginResult {
    InlinePluginResult {
        code: None,
        diagnostics: vec![PluginDiagnostic {
            stable_ptr: macro_ast.arguments(db).left_bracket_stable_ptr(db),
            message: format!(
                "Macro `{}` does not support this bracket type.",
                macro_ast.path(db).as_syntax_node().get_text_without_trivia(db)
            ),
        }],
    }
}

/// Extracts a single unnamed argument.
pub fn extract_single_unnamed_arg(
    db: &dyn SyntaxGroup,
    macro_arguments: ast::ArgList,
) -> Option<ast::Expr> {
    if let Ok([arg]) = <[_; 1]>::try_from(macro_arguments.elements(db)) {
        try_extract_unnamed_arg(db, &arg)
    } else {
        None
    }
}

/// Extracts `n` unnamed arguments.
pub fn extract_unnamed_args(
    db: &dyn SyntaxGroup,
    macro_arguments: &ast::ArgList,
    n: usize,
) -> Option<Vec<ast::Expr>> {
    let elements = macro_arguments.elements(db);
    if elements.len() != n {
        return None;
    }
    elements.iter().map(|x| try_extract_unnamed_arg(db, x)).collect()
}

/// Gets the syntax of an argument, and extracts the value if it is unnamed.
pub fn try_extract_unnamed_arg(db: &dyn SyntaxGroup, arg_ast: &ast::Arg) -> Option<ast::Expr> {
    if let ast::ArgClause::Unnamed(arg_clause) = arg_ast.arg_clause(db) {
        Some(arg_clause.value(db))
    } else {
        None
    }
}

/// Escapes a node for use in a format string.
pub fn escape_node(db: &dyn SyntaxGroup, node: SyntaxNode) -> String {
    node.get_text_without_trivia(db).replace('{', "{{").replace('}', "}}").escape_unicode().join("")
}

/// Macro to extract unnamed arguments of an inline macro.
/// Gets the expected number of unnamed arguments, and the pattern for the allowed bracket types,
/// and returns a fixed size array with the argument expressions.
///
/// Example usage (2 arguments, allowing `()` or `{}` brackets):
/// let [arg1, arg2] = extract_macro_unnamed_args!(
///     db,
///     syntax,
///     2,
///     ast::WrappedArgList::ParenthesizedArgList(_) | ast::WrappedArgList::BracedArgList(_)
/// );
#[macro_export]
macro_rules! extract_macro_unnamed_args {
    ($db:expr, $syntax:expr, $n:expr, $pattern:pat) => {{
        if !matches!($syntax.arguments($db), $pattern) {
            return $crate::inline_macros::unsupported_bracket_diagnostic($db, $syntax);
        }
        // `unwrap` is ok because the above `matches` condition ensures it's not None (unless
        // the pattern contains the `Missing` variant).
        let macro_arg_list = cairo_lang_syntax::node::helpers::WrappedArgListHelper::arg_list(
            &$syntax.arguments($db),
            $db,
        )
        .unwrap();

        let args = $crate::inline_macros::extract_unnamed_args($db, &macro_arg_list, $n);
        let Some(args) = args else {
            let diagnostics = vec![PluginDiagnostic {
                stable_ptr: $syntax.stable_ptr().untyped(),
                message: format!(
                    "Macro `{}` must have exactly {} unnamed arguments.",
                    $syntax.path($db).as_syntax_node().get_text_without_trivia($db),
                    $n
                ),
            }];
            return InlinePluginResult { code: None, diagnostics };
        };
        let args: [ast::Expr; $n] = args.try_into().unwrap();
        args
    }};
}

/// Macro to extract a single unnamed argument of an inline macro.
/// Gets the pattern for the allowed bracket types, and returns the argument expression.
///
/// Example usage (allowing `()` or `{}` brackets):
/// let arg = extract_macro_single_unnamed_arg!(
///     db,
///     syntax,
///     ast::WrappedArgList::ParenthesizedArgList(_) | ast::WrappedArgList::BracedArgList(_)
/// );
#[macro_export]
macro_rules! extract_macro_single_unnamed_arg {
    ($db:expr, $syntax:expr, $pattern:pat) => {{
        let [x] = $crate::extract_macro_unnamed_args!($db, $syntax, 1, $pattern);
        x
    }};
}