use std::fmt;
use cairo_lang_debug::DebugWithDb;
use cairo_lang_filesystem::ids::SmolStrId;
use salsa::Database;
use crate::node::{Terminal, TypedSyntaxNode, ast};
#[derive(Clone, Debug, PartialEq, Eq, salsa::Update)]
pub struct Attribute<'a> {
pub stable_ptr: ast::AttributePtr<'a>,
pub id: SmolStrId<'a>,
pub id_stable_ptr: ast::ExprPathPtr<'a>,
pub args: Vec<AttributeArg<'a>>,
pub args_stable_ptr: ast::OptionArgListParenthesizedPtr<'a>,
}
impl<'a> Attribute<'a> {
pub fn is_single_unnamed_arg(&self, db: &'a dyn Database, arg_name: &str) -> bool {
match &self.args[..] {
[arg] => match &arg.variant {
AttributeArgVariant::Unnamed(value) => {
value.as_syntax_node().get_text_without_trivia(db).long(db) == arg_name
}
_ => false,
},
_ => false,
}
}
}
#[derive(Clone, Debug, PartialEq, Eq, salsa::Update)]
pub struct AttributeArg<'a> {
pub variant: AttributeArgVariant<'a>,
pub arg: ast::Arg<'a>,
pub modifiers: Vec<Modifier<'a>>,
}
#[derive(Clone, Debug, PartialEq, Eq, salsa::Update)]
pub enum AttributeArgVariant<'a> {
Unnamed(ast::Expr<'a>),
Named { value: ast::Expr<'a>, name: NameInfo<'a> },
FieldInitShorthand(NameInfo<'a>),
}
#[derive(Clone, Debug, PartialEq, Eq, salsa::Update)]
pub struct NameInfo<'a> {
pub text: SmolStrId<'a>,
pub stable_ptr: ast::TerminalIdentifierPtr<'a>,
}
impl<'a> NameInfo<'a> {
fn from_ast(name: &ast::TerminalIdentifier<'a>, db: &'a dyn Database) -> Self {
NameInfo { text: name.text(db), stable_ptr: name.stable_ptr(db) }
}
}
#[derive(Clone, Debug, PartialEq, Eq, salsa::Update)]
pub struct Modifier<'a> {
pub text: SmolStrId<'a>,
pub stable_ptr: ast::ModifierPtr<'a>,
}
impl<'a> DebugWithDb<'a> for Attribute<'a> {
type Db = dyn Database;
fn fmt(&self, f: &mut fmt::Formatter<'_>, db: &'a dyn Database) -> fmt::Result {
write!(f, r#"Attribute {{ id: "{}""#, self.id.long(db))?;
if !self.args.is_empty() {
write!(f, ", args: [")?;
for arg in &self.args {
write!(f, "{:?}, ", arg.arg.as_syntax_node().get_text(db))?;
}
write!(f, "]")?;
}
write!(f, " }}")
}
}
pub trait AttributeStructurize<'a> {
fn structurize(self, db: &'a dyn Database) -> Attribute<'a>;
}
impl<'a> AttributeStructurize<'a> for ast::Attribute<'a> {
fn structurize(self, db: &'a dyn Database) -> Attribute<'a> {
let attr_id = self.attr(db);
let attr_args = self.arguments(db);
Attribute {
stable_ptr: self.stable_ptr(db),
id: attr_id.as_syntax_node().get_text_without_trivia(db),
id_stable_ptr: attr_id.stable_ptr(db),
args: match attr_args {
ast::OptionArgListParenthesized::ArgListParenthesized(ref attribute_args) => {
attribute_args
.arguments(db)
.elements(db)
.map(|arg| AttributeArg::from_ast(arg, db))
.collect()
}
ast::OptionArgListParenthesized::Empty(_) => vec![],
},
args_stable_ptr: attr_args.stable_ptr(db),
}
}
}
pub trait AttributeListStructurize<'a> {
fn structurize(self, db: &'a dyn Database) -> Vec<Attribute<'a>>;
}
impl<'a> AttributeListStructurize<'a> for ast::AttributeList<'a> {
fn structurize(self, db: &'a dyn Database) -> Vec<Attribute<'a>> {
self.elements(db).map(|attr| attr.structurize(db)).collect()
}
}
impl<'a> AttributeArg<'a> {
pub fn from_ast(arg: ast::Arg<'a>, db: &'a dyn Database) -> AttributeArg<'a> {
let variant = match arg.arg_clause(db) {
ast::ArgClause::Unnamed(clause) => AttributeArgVariant::Unnamed(clause.value(db)),
ast::ArgClause::Named(clause) => AttributeArgVariant::Named {
value: clause.value(db),
name: NameInfo::from_ast(&clause.name(db), db),
},
ast::ArgClause::FieldInitShorthand(clause) => AttributeArgVariant::FieldInitShorthand(
NameInfo::from_ast(&clause.name(db).name(db), db),
),
};
let modifiers =
arg.modifiers(db).elements(db).map(|modifier| Modifier::from(modifier, db)).collect();
AttributeArg { variant, arg, modifiers }
}
pub fn text(&self, db: &dyn Database) -> String {
match &self.variant {
AttributeArgVariant::Unnamed(value) => {
value.as_syntax_node().get_text_without_trivia(db).to_string(db)
}
AttributeArgVariant::Named { value, name } => {
format!(
"{}: {}",
name.text.long(db),
value.as_syntax_node().get_text_without_trivia(db).long(db)
)
}
AttributeArgVariant::FieldInitShorthand(name) => {
format!(":{}", name.text.long(db))
}
}
}
}
impl<'a> Modifier<'a> {
fn from(modifier: ast::Modifier<'a>, db: &'a dyn Database) -> Modifier<'a> {
let text = match &modifier {
ast::Modifier::Ref(r) => r.text(db),
ast::Modifier::Mut(m) => m.text(db),
};
Modifier { stable_ptr: modifier.stable_ptr(db), text }
}
}