use crate::ast::{AttributeBody, AttributeBodyElement, AttributeDef, AttributeUsage, Node};
use crate::parser::body::parse_structured_brace_members;
use crate::parser::build_recovery_error_node_from_span;
use crate::parser::expr::expression;
use crate::parser::lex::{
identification, name, qualified_name, starts_with_keyword, ws1, ws_and_comments,
};
use crate::parser::node_from_to;
use crate::parser::requirement::doc_comment;
use crate::parser::usage::{multiplicity, optional_typings, specialization_clauses, typings};
use crate::parser::with_span;
use crate::parser::Input;
use nom::branch::alt;
use nom::bytes::complete::tag;
use nom::combinator::{map, value};
use nom::multi::many0;
use nom::sequence::preceded;
use nom::IResult;
use nom::Parser;
const ATTRIBUTE_BODY_STARTERS: &[&[u8]] = &[b"doc", b"attribute", b"comment", b"@", b"#"];
fn local_name_from_qualified_name(qname: &str) -> String {
qname.rsplit("::").next().unwrap_or(qname).to_string()
}
fn is_reserved_shorthand_starter(name: &str) -> bool {
matches!(
name,
"interface"
| "part"
| "connect"
| "bind"
| "perform"
| "allocate"
| "port"
| "state"
| "satisfy"
| "action"
| "attribute"
| "ref"
| "doc"
| "metadata"
| "filter"
| "use"
| "view"
| "viewpoint"
| "render"
| "rendering"
| "requirement"
| "require"
| "concern"
| "actor"
| "item"
| "individual"
| "constraint"
| "calc"
| "enum"
| "occurrence"
)
}
fn ignored_feature_modifiers(input: Input<'_>) -> IResult<Input<'_>, ()> {
let (input, _) = many0(preceded(
ws_and_comments,
alt((
value((), multiplicity),
value((), tag(&b"nonunique"[..])),
value((), tag(&b"unique"[..])),
value((), tag(&b"ordered"[..])),
value((), tag(&b"nonordered"[..])),
)),
))
.parse(input)?;
Ok((input, ()))
}
fn value_part(input: Input<'_>) -> IResult<Input<'_>, Node<crate::ast::Expression>> {
let (input, _) = ws_and_comments(input)?;
let (input, _) = alt((
preceded(tag(&b"="[..]), ws_and_comments),
preceded(tag(&b":="[..]), ws_and_comments),
preceded(
preceded(tag(&b"default"[..]), ws1),
alt((
preceded(alt((tag(&b"="[..]), tag(&b":="[..]))), ws_and_comments),
ws_and_comments,
)),
),
))
.parse(input)?;
expression(input)
}
fn attribute_body_element(input: Input<'_>) -> IResult<Input<'_>, Node<AttributeBodyElement>> {
let start = input;
let (input, _) = ws_and_comments(input)?;
let (input, elem) = alt((
map(doc_comment, AttributeBodyElement::Doc),
map(
|i| attribute_def(i, true),
AttributeBodyElement::AttributeDef,
),
map(attribute_usage, AttributeBodyElement::AttributeUsage),
))
.parse(input)?;
Ok((input, node_from_to(start, input, elem)))
}
fn attribute_body_recovery(start: Input<'_>, end: Input<'_>) -> Node<AttributeBodyElement> {
let recovery = build_recovery_error_node_from_span(
start,
end,
ATTRIBUTE_BODY_STARTERS,
"attribute body",
"recovered_attribute_body_element",
);
node_from_to(
start,
end,
AttributeBodyElement::Error(node_from_to(start, end, recovery)),
)
}
pub(crate) fn attribute_body(input: Input<'_>) -> IResult<Input<'_>, AttributeBody> {
let (input, _) = ws_and_comments(input)?;
if input.fragment().starts_with(b";") {
let (input, _) = tag(&b";"[..]).parse(input)?;
return Ok((input, AttributeBody::Semicolon));
}
let (input, elements) = parse_structured_brace_members(
input,
ATTRIBUTE_BODY_STARTERS,
"attribute body",
"recovered_attribute_body_element",
attribute_body_element,
attribute_body_recovery,
)?;
Ok((input, AttributeBody::Brace { elements }))
}
pub(crate) fn attribute_def(
input: Input<'_>,
disambiguate_from_usage: bool,
) -> IResult<Input<'_>, Node<AttributeDef>> {
let start = input;
let (input, _) = ws_and_comments(input)?;
let (input, _) = nom::combinator::opt(preceded(
alt((
tag(&b"private"[..]),
tag(&b"protected"[..]),
tag(&b"public"[..]),
)),
ws1,
))
.parse(input)?;
let (input, _) = nom::combinator::opt(preceded(tag(&b"abstract"[..]), ws1)).parse(input)?;
let (input, _) = tag(&b"attribute"[..]).parse(input)?;
let (input, _) = ws1(input)?;
let (input, has_def) = nom::combinator::opt(preceded(tag(&b"def"[..]), ws1)).parse(input)?;
let has_def = has_def.is_some();
let ident_start = input;
let (input, ident) = identification(input)?;
let name_span = ident
.name
.as_ref()
.map(|_| crate::parser::span_from_to(ident_start, input));
let Some(name_str) = ident.name.clone() else {
return Err(nom::Err::Error(nom::error::Error::new(
input,
nom::error::ErrorKind::Tag,
)));
};
if disambiguate_from_usage {
let (peek_input, _) = ws_and_comments(input)?;
let peek = peek_input.fragment();
if starts_with_keyword(peek, b"redefines") || starts_with_keyword(peek, b":>>") {
return Err(nom::Err::Error(nom::error::Error::new(
peek_input,
nom::error::ErrorKind::Tag,
)));
}
}
let (input, typing_result) = optional_typings(input)?;
let (typing_span, typing) = typing_result
.map(|(span, s)| (Some(span), Some(s)))
.unwrap_or((None, None));
let (input, _) = ignored_feature_modifiers(input)?;
if disambiguate_from_usage && !has_def && typing.is_none() {
let (peek_input, _) = ws_and_comments(input)?;
let peek = peek_input.fragment();
if peek.starts_with(b"=") || peek.starts_with(b":=") {
return Err(nom::Err::Error(nom::error::Error::new(
peek_input,
nom::error::ErrorKind::Tag,
)));
}
}
let (input, leading_clauses) = specialization_clauses(input)?;
let leading_subset = leading_clauses.subsets;
let (typing_span, typing, leading_value) = if typing.is_none() {
leading_subset
.map(|(name, value)| (None, Some(name), value))
.unwrap_or((typing_span, typing, None))
} else {
(typing_span, typing, None)
};
let (input, value) =
nom::combinator::opt(preceded(ws_and_comments, value_part)).parse(input)?;
let value = value.or(leading_value);
let value_span = value.as_ref().map(|node| node.span.clone());
let (input, _) = specialization_clauses(input)?;
let (input, _) = ignored_feature_modifiers(input)?;
let (input, body) = attribute_body(input)?;
Ok((
input,
node_from_to(
start,
input,
AttributeDef {
name: name_str,
typing,
value,
body,
name_span,
typing_span,
value_span,
},
),
))
}
pub(crate) fn attribute_usage(input: Input<'_>) -> IResult<Input<'_>, Node<AttributeUsage>> {
enum AttributeUsageHead {
Named {
name_span: crate::ast::Span,
name: String,
},
PrefixRedefines {
redefines_span: crate::ast::Span,
redefines: String,
},
}
let start = input;
let (input, _) = ws_and_comments(input)?;
let (input, _) = tag(&b"attribute"[..]).parse(input)?;
let (input, _) = ws1(input)?;
let (input, usage_head) = alt((
map(
preceded(
preceded(ws_and_comments, tag(&b":>>"[..])),
preceded(ws_and_comments, with_span(qualified_name)),
),
|(redefines_span, redefines)| AttributeUsageHead::PrefixRedefines {
redefines_span,
redefines,
},
),
map(with_span(name), |(name_span, name)| {
AttributeUsageHead::Named { name_span, name }
}),
))
.parse(input)?;
let (input, name_span, name_str, typing_span, typing, redefines_span, redefines) =
match usage_head {
AttributeUsageHead::PrefixRedefines {
redefines_span,
redefines,
} => {
let (input, typing_result) = optional_typings(input)?;
let (typing_span, typing) = typing_result
.map(|(span, s)| (Some(span), Some(s)))
.unwrap_or((None, None));
let (input, _) = ignored_feature_modifiers(input)?;
(
input,
None,
local_name_from_qualified_name(&redefines),
typing_span,
typing,
Some(redefines_span),
Some(redefines),
)
}
AttributeUsageHead::Named { name_span, name } => {
let (input, typing_result) = optional_typings(input)?;
let (typing_span, typing) = typing_result
.map(|(span, s)| (Some(span), Some(s)))
.unwrap_or((None, None));
let (input, _) = ignored_feature_modifiers(input)?;
(
input,
Some(name_span),
name,
typing_span,
typing,
None,
None,
)
}
};
let (input, leading_clauses) = specialization_clauses(input)?;
let (input, _) = ignored_feature_modifiers(input)?;
let (input, value) =
nom::combinator::opt(preceded(ws_and_comments, value_part)).parse(input)?;
let (input, trailing_clauses) = specialization_clauses(input)?;
let (input, _) = ignored_feature_modifiers(input)?;
let redefines = trailing_clauses
.redefines
.or(leading_clauses.redefines)
.or(redefines);
let subsets = trailing_clauses
.subsets
.or(leading_clauses.subsets)
.map(|(target, _)| target);
let references = trailing_clauses.references.or(leading_clauses.references);
let crosses = trailing_clauses.crosses.or(leading_clauses.crosses);
let (input, body) = attribute_body(input)?;
Ok((
input,
node_from_to(
start,
input,
AttributeUsage {
name: name_str,
typing,
subsets,
redefines,
references,
crosses,
value,
body,
name_span,
typing_span,
redefines_span,
},
),
))
}
pub(crate) fn attribute_usage_shorthand(
input: Input<'_>,
) -> IResult<Input<'_>, Node<AttributeUsage>> {
let start = input;
let (input, _) = ws_and_comments(input)?;
let (input, _) =
nom::combinator::opt(preceded(ws_and_comments, tag(&b":>>"[..]))).parse(input)?;
let (input, (name_span, name_str)) = with_span(name).parse(input)?;
if is_reserved_shorthand_starter(&name_str) {
return Err(nom::Err::Error(nom::error::Error::new(
start,
nom::error::ErrorKind::Tag,
)));
}
let (input, _) = typings(input)?;
let (input, value) =
nom::combinator::opt(preceded(ws_and_comments, value_part)).parse(input)?;
let (input, _) = preceded(ws_and_comments, tag(&b";"[..])).parse(input)?;
Ok((
input,
node_from_to(
start,
input,
AttributeUsage {
name: name_str,
typing: None,
subsets: None,
redefines: None,
references: None,
crosses: None,
value,
body: AttributeBody::Semicolon,
name_span: Some(name_span),
typing_span: None,
redefines_span: None,
},
),
))
}