use cairo_lang_syntax::node::helpers::WrappedArgListHelper;
use cairo_lang_syntax::node::ids::SyntaxStablePtrId;
use cairo_lang_syntax::node::{SyntaxNode, TypedSyntaxNode, ast};
use cairo_lang_utils::require;
use itertools::Itertools;
use salsa::Database;
use crate::plugin::{InlinePluginResult, PluginDiagnostic, PluginResult};
pub trait InlineMacroCall<'db> {
type PathNode: TypedSyntaxNode<'db>;
type Result: PluginResultTrait<'db>;
fn arguments(&self, db: &'db dyn Database) -> ast::WrappedArgList<'db>;
fn path(&self, db: &'db dyn Database) -> Self::PathNode;
}
impl<'db> InlineMacroCall<'db> for ast::LegacyExprInlineMacro<'db> {
type PathNode = ast::ExprPath<'db>;
type Result = InlinePluginResult<'db>;
fn arguments(&self, db: &'db dyn Database) -> ast::WrappedArgList<'db> {
self.arguments(db)
}
fn path(&self, db: &'db dyn Database) -> ast::ExprPath<'db> {
self.path(db)
}
}
impl<'db> InlineMacroCall<'db> for ast::LegacyItemInlineMacro<'db> {
type PathNode = ast::ExprPath<'db>;
type Result = PluginResult<'db>;
fn arguments(&self, db: &'db dyn Database) -> ast::WrappedArgList<'db> {
self.arguments(db)
}
fn path(&self, db: &'db dyn Database) -> ast::ExprPath<'db> {
self.path(db)
}
}
pub trait PluginResultTrait<'db> {
fn diagnostic_only(diagnostic: PluginDiagnostic<'db>) -> Self;
}
impl<'db> PluginResultTrait<'db> for InlinePluginResult<'db> {
fn diagnostic_only(diagnostic: PluginDiagnostic<'db>) -> Self {
InlinePluginResult { code: None, diagnostics: vec![diagnostic] }
}
}
impl<'db> PluginResultTrait<'db> for PluginResult<'db> {
fn diagnostic_only(diagnostic: PluginDiagnostic<'db>) -> Self {
PluginResult { code: None, diagnostics: vec![diagnostic], remove_original_item: true }
}
}
pub fn unsupported_bracket_diagnostic<'db, CallAst: InlineMacroCall<'db>>(
db: &'db dyn Database,
legacy_macro_ast: &CallAst,
macro_ast: impl Into<SyntaxStablePtrId<'db>>,
) -> CallAst::Result {
CallAst::Result::diagnostic_only(PluginDiagnostic::error_with_inner_span(
db,
macro_ast,
legacy_macro_ast.arguments(db).left_bracket_syntax_node(db),
format!(
"Macro `{}` does not support this bracket type.",
legacy_macro_ast.path(db).as_syntax_node().get_text_without_trivia(db).long(db)
),
))
}
pub fn not_legacy_macro_diagnostic(stable_ptr: SyntaxStablePtrId<'_>) -> PluginDiagnostic<'_> {
PluginDiagnostic::error(
stable_ptr,
"Macro cannot be parsed as legacy macro. Expected an argument list wrapped in either \
parentheses, brackets, or braces."
.to_string(),
)
}
pub fn extract_single_unnamed_arg<'db>(
db: &'db dyn Database,
macro_arguments: ast::ArgList<'db>,
) -> Option<ast::Expr<'db>> {
let mut elements = macro_arguments.elements(db);
if elements.len() == 1 { try_extract_unnamed_arg(db, &elements.next()?) } else { None }
}
pub fn extract_unnamed_args<'db>(
db: &'db dyn Database,
macro_arguments: &ast::ArgList<'db>,
n: usize,
) -> Option<Vec<ast::Expr<'db>>> {
let elements = macro_arguments.elements(db);
require(elements.len() == n)?;
elements.map(|x| try_extract_unnamed_arg(db, &x)).collect()
}
pub fn try_extract_unnamed_arg<'db>(
db: &'db dyn Database,
arg_ast: &ast::Arg<'db>,
) -> Option<ast::Expr<'db>> {
if let ast::ArgClause::Unnamed(arg_clause) = arg_ast.arg_clause(db) {
Some(arg_clause.value(db))
} else {
None
}
}
pub fn escape_node(db: &dyn Database, node: SyntaxNode<'_>) -> String {
node.get_text_without_trivia(db)
.long(db)
.replace('{', "{{")
.replace('}', "}}")
.escape_unicode()
.join("")
}
#[macro_export]
macro_rules! extract_macro_unnamed_args {
($db:expr, $syntax:expr, $n:expr, $pattern:pat, $diagnostics_ptr:expr) => {{
let arguments = $crate::plugin_utils::InlineMacroCall::arguments($syntax, $db);
if !matches!(arguments, $pattern) {
return $crate::plugin_utils::unsupported_bracket_diagnostic(
$db,
$syntax,
$diagnostics_ptr,
);
}
let macro_arg_list =
cairo_lang_syntax::node::helpers::WrappedArgListHelper::arg_list(&arguments, $db)
.unwrap();
let args = $crate::plugin_utils::extract_unnamed_args($db, ¯o_arg_list, $n);
let Some(args) = args else {
return $crate::plugin_utils::PluginResultTrait::diagnostic_only(
PluginDiagnostic::error(
$diagnostics_ptr,
format!(
"Macro `{}` must have exactly {} unnamed arguments.",
$crate::plugin_utils::InlineMacroCall::path($syntax, $db)
.as_syntax_node()
.get_text_without_trivia($db)
.long($db),
$n
),
),
);
};
let args: [ast::Expr; $n] = args.try_into().unwrap();
args
}};
}
#[macro_export]
macro_rules! extract_macro_single_unnamed_arg {
($db:expr, $syntax:expr, $pattern:pat, $diagnostics_ptr:expr) => {{
let [x] = $crate::extract_macro_unnamed_args!($db, $syntax, 1, $pattern, $diagnostics_ptr);
x
}};
}