use crate::ast::{
ClassifierDecl, ExtendedLibraryDecl, FeatureDecl, FilterMember, KermlFeatureDecl,
KermlSemanticDecl, LibraryPackage, NamespaceDecl, Node, Package, PackageBody,
PackageBodyElement, RootElement, RootNamespace, Visibility,
};
use crate::parser::action::{action_def, action_usage};
use crate::parser::alias::alias_def;
use crate::parser::allocation::{allocate_usage, allocation_def, allocation_usage};
use crate::parser::attribute::attribute_def;
use crate::parser::build_recovery_error_node_from_span;
use crate::parser::case::{
analysis_case_def, analysis_case_usage, case_def, case_usage, verification_case_def,
verification_case_usage,
};
use crate::parser::connection::connection_def;
use crate::parser::constraint::{calc_def, constraint_def};
use crate::parser::dependency::dependency;
use crate::parser::enumeration::enum_def;
use crate::parser::expr::expression;
use crate::parser::flow::{flow_def, flow_usage};
use crate::parser::import::import_;
use crate::parser::individual::individual_def;
use crate::parser::interface::interface_def;
use crate::parser::item::item_def;
use crate::parser::lex::{
name, qualified_name, recover_body_element, skip_statement_or_block, starts_with_any_keyword,
starts_with_keyword, ws1, ws_and_comments, PACKAGE_BODY_STARTERS,
};
use crate::parser::metadata::metadata_def;
use crate::parser::node_from_to;
use crate::parser::occurrence::{
individual_usage, occurrence_def, occurrence_usage, snapshot_usage, timeslice_usage,
};
use crate::parser::part::{part_def_or_usage, PartDefOrUsage};
use crate::parser::port::port_def;
use crate::parser::requirement::{
comment_annotation, concern_usage, doc_comment, requirement_def, requirement_usage, satisfy,
textual_representation,
};
use crate::parser::state::state_def;
use crate::parser::state::state_usage;
use crate::parser::usecase::{actor_decl, use_case_def, use_case_usage};
use crate::parser::view::{
rendering_def, rendering_usage, view_def, view_usage, viewpoint_def, viewpoint_usage,
};
use crate::parser::Input;
use nom::branch::alt;
use nom::bytes::complete::tag;
use nom::combinator::{map, opt};
use nom::multi::many0;
use nom::sequence::{delimited, preceded};
use nom::IResult;
use nom::Parser;
fn keyword_package(input: Input<'_>) -> IResult<Input<'_>, ()> {
let (input, _) = tag(&b"package"[..]).parse(input)?;
let (input, _) = ws1(input)?;
Ok((input, ()))
}
fn required_package_identification(
input: Input<'_>,
) -> IResult<Input<'_>, crate::ast::Identification> {
let (input, short_name) = opt(delimited(
preceded(ws_and_comments, tag(&b"<"[..])),
preceded(ws_and_comments, name),
preceded(ws_and_comments, tag(&b">"[..])),
))
.parse(input)?;
let (input, decl_name) = opt(preceded(ws_and_comments, qualified_name)).parse(input)?;
if short_name.is_some() || decl_name.is_some() {
Ok((
input,
crate::ast::Identification {
short_name,
name: decl_name,
},
))
} else {
Err(nom::Err::Error(nom::error::Error::new(
input,
nom::error::ErrorKind::Tag,
)))
}
}
fn library_package_(input: Input<'_>) -> IResult<Input<'_>, Node<LibraryPackage>> {
let start = input;
let (input, _) = ws_and_comments(input)?;
let (input, is_standard) = if input.fragment().starts_with(b"standard") {
let (input, _) = tag(&b"standard"[..]).parse(input)?;
let (input, _) = ws1(input)?;
let (input, _) = tag(&b"library"[..]).parse(input)?;
let (input, _) = ws1(input)?;
(input, true)
} else {
let (input, _) = tag(&b"library"[..]).parse(input)?;
let (input, _) = ws1(input)?;
let (input, is_standard) = opt(preceded(tag(&b"standard"[..]), ws1))
.parse(input)
.map(|(i, o)| (i, o.is_some()))?;
(input, is_standard)
};
let (input, _) = keyword_package(input)?;
let (input, identification) = required_package_identification(input)?;
let (input, body) = package_body(input)?;
Ok((
input,
node_from_to(
start,
input,
LibraryPackage {
is_standard,
identification,
body,
},
),
))
}
fn package_(input: Input<'_>) -> IResult<Input<'_>, Node<Package>> {
let start = input;
let (input, _) = keyword_package(input)?;
let (input, identification) = required_package_identification(input)?;
let (input, body) = package_body(input)?;
Ok((
input,
node_from_to(
start,
input,
Package {
identification,
body,
},
),
))
}
fn namespace_decl(input: Input<'_>) -> IResult<Input<'_>, Node<NamespaceDecl>> {
let start = input;
let (input, _) = preceded(ws_and_comments, tag(&b"namespace"[..])).parse(input)?;
let (input, _) = ws1(input)?;
let (input, identification) = required_package_identification(input)?;
let (input, body) = package_body(input)?;
Ok((
input,
node_from_to(
start,
input,
NamespaceDecl {
identification,
body,
},
),
))
}
pub(crate) fn root_element(input: Input<'_>) -> IResult<Input<'_>, Node<RootElement>> {
let (input, _) = ws_and_comments(input)?;
let start = input;
let (input, elem) = alt((
map(import_, RootElement::Import),
map(namespace_decl, RootElement::Namespace),
map(library_package_, RootElement::LibraryPackage),
map(package_, RootElement::Package),
))
.parse(input)?;
Ok((input, node_from_to(start, input, elem)))
}
pub(crate) fn package_body(input: Input<'_>) -> IResult<Input<'_>, PackageBody> {
alt((
package_body_brace,
map(preceded(ws_and_comments, tag(&b";"[..])), |_| {
PackageBody::Semicolon
}),
))
.parse(input)
}
fn package_body_element_fallback(input: Input<'_>) -> IResult<Input<'_>, Node<PackageBodyElement>> {
let (input, _) = ws_and_comments(input)?;
let frag = input.fragment();
if starts_with_keyword(frag, b"part")
|| starts_with_keyword(frag, b"abstract")
|| starts_with_keyword(frag, b"variation")
{
let start = input;
let (input, parsed) = part_def_or_usage(input)?;
let value = match parsed {
PartDefOrUsage::Def(n) => PackageBodyElement::PartDef(n),
PartDefOrUsage::Usage(n) => PackageBodyElement::PartUsage(n),
};
return Ok((input, node_from_to(start, input, value)));
}
Err(nom::Err::Error(nom::error::Error::new(
input,
nom::error::ErrorKind::Tag,
)))
}
fn modeled_decl_text(start: Input<'_>, end: Input<'_>) -> String {
let delta = end
.location_offset()
.saturating_sub(start.location_offset());
let bytes = start.fragment();
let take = delta.min(bytes.len());
String::from_utf8_lossy(&bytes[..take]).trim().to_string()
}
fn starts_with_visibility_prefix(fragment: &[u8]) -> Option<usize> {
for prefix in [
b"public".as_slice(),
b"private".as_slice(),
b"protected".as_slice(),
] {
if starts_with_keyword(fragment, prefix) {
return Some(prefix.len());
}
}
None
}
fn strip_common_decl_prefixes(fragment: &[u8]) -> &[u8] {
let mut frag = fragment;
if let Some(len) = starts_with_visibility_prefix(frag) {
frag = &frag[len..];
let mut i = 0usize;
while i < frag.len() && frag[i].is_ascii_whitespace() {
i += 1;
}
frag = &frag[i..];
}
if starts_with_keyword(frag, b"abstract") || starts_with_keyword(frag, b"variation") {
let cut = if starts_with_keyword(frag, b"abstract") {
8
} else {
9
};
frag = &frag[cut..];
let mut i = 0usize;
while i < frag.len() && frag[i].is_ascii_whitespace() {
i += 1;
}
frag = &frag[i..];
}
frag
}
fn is_modeled_decl_start(fragment: &[u8], starters: &[&[u8]]) -> bool {
if fragment.starts_with(b"#") {
return false;
}
if starts_with_keyword(fragment, b"package")
|| starts_with_keyword(fragment, b"library")
|| starts_with_keyword(fragment, b"namespace")
|| starts_with_keyword(fragment, b"import")
|| starts_with_keyword(fragment, b"doc")
|| starts_with_keyword(fragment, b"comment")
|| starts_with_keyword(fragment, b"filter")
{
return false;
}
let frag = strip_common_decl_prefixes(fragment);
starts_with_any_keyword(frag, starters)
}
fn parse_modeled_decl<'a>(
input: Input<'a>,
starters: &'a [&'a [u8]],
) -> IResult<Input<'a>, (String, String)> {
let (input, _) = ws_and_comments(input)?;
if input.fragment().is_empty() || input.fragment().starts_with(b"}") {
return Err(nom::Err::Error(nom::error::Error::new(
input,
nom::error::ErrorKind::Tag,
)));
}
if !is_modeled_decl_start(input.fragment(), starters) {
return Err(nom::Err::Error(nom::error::Error::new(
input,
nom::error::ErrorKind::Tag,
)));
}
let raw_start = input;
let stripped = strip_common_decl_prefixes(input.fragment());
let bnf_production = starters
.iter()
.find(|kw| starts_with_keyword(stripped, kw))
.map(|kw| String::from_utf8_lossy(kw).to_string())
.unwrap_or_else(|| "declaration".to_string());
let (input, _) = skip_statement_or_block(input)?;
Ok((input, (bnf_production, modeled_decl_text(raw_start, input))))
}
fn kerml_semantic_decl(input: Input<'_>) -> IResult<Input<'_>, Node<KermlSemanticDecl>> {
let start = input;
let starters: &[&[u8]] = &[
b"behavior",
b"bool",
b"function",
b"interaction",
b"datatype",
b"inv",
b"invariant",
b"multiplicity",
b"assoc",
b"association",
b"metaclass",
b"step",
];
let (input, (bnf_production, text)) = parse_modeled_decl(input, starters)?;
Ok((
input,
node_from_to(
start,
input,
KermlSemanticDecl {
bnf_production,
text,
},
),
))
}
fn kerml_feature_decl(input: Input<'_>) -> IResult<Input<'_>, Node<KermlFeatureDecl>> {
let start = input;
let starters: &[&[u8]] = &[b"occurrence", b"expr", b"predicate", b"succession"];
let (input, (bnf_production, text)) = parse_modeled_decl(input, starters)?;
Ok((
input,
node_from_to(
start,
input,
KermlFeatureDecl {
bnf_production,
text,
},
),
))
}
fn feature_decl(input: Input<'_>) -> IResult<Input<'_>, Node<FeatureDecl>> {
let start = input;
let starters: &[&[u8]] = &[b"feature"];
let (input, (keyword, text)) = parse_modeled_decl(input, starters)?;
Ok((
input,
node_from_to(start, input, FeatureDecl { keyword, text }),
))
}
fn classifier_decl(input: Input<'_>) -> IResult<Input<'_>, Node<ClassifierDecl>> {
let start = input;
let starters: &[&[u8]] = &[
b"class",
b"classifier",
b"struct",
b"structure",
b"subclassifier",
];
let (input, (keyword, text)) = parse_modeled_decl(input, starters)?;
Ok((
input,
node_from_to(start, input, ClassifierDecl { keyword, text }),
))
}
fn extended_library_decl(input: Input<'_>) -> IResult<Input<'_>, Node<ExtendedLibraryDecl>> {
let start = input;
let starters: &[&[u8]] = &[
b"action",
b"allocation",
b"analysis",
b"attribute",
b"case",
b"calc",
b"connection",
b"constraint",
b"flow",
b"interface",
b"item",
b"metadata",
b"requirement",
b"state",
b"use",
b"verification",
b"view",
b"viewpoint",
b"rendering",
b"enum",
b"message",
b"concern",
b"part",
b"port",
];
let (input, (bnf_production, text)) = parse_modeled_decl(input, starters)?;
Ok((
input,
node_from_to(
start,
input,
ExtendedLibraryDecl {
bnf_production,
text,
},
),
))
}
fn package_body_brace(input: Input<'_>) -> IResult<Input<'_>, PackageBody> {
let (mut input, _) = preceded(ws_and_comments, tag(&b"{"[..])).parse(input)?;
let mut elements = Vec::new();
loop {
let (next, _) = ws_and_comments(input)?;
input = next;
if input.fragment().is_empty() {
return Err(nom::Err::Error(nom::error::Error::new(
input,
nom::error::ErrorKind::Eof,
)));
}
if input.fragment().starts_with(b"}") {
let (input, _) = preceded(ws_and_comments, tag(&b"}"[..])).parse(input)?;
return Ok((input, PackageBody::Brace { elements }));
}
match package_body_element(input) {
Ok((next, element)) => {
if next.location_offset() == input.location_offset() {
return Err(nom::Err::Failure(nom::error::Error::new(
input,
nom::error::ErrorKind::Many0,
)));
}
elements.push(element);
input = next;
}
Err(_)
if starts_with_any_keyword(input.fragment(), PACKAGE_BODY_STARTERS)
|| starts_with_any_keyword(
strip_common_decl_prefixes(input.fragment()),
PACKAGE_BODY_STARTERS,
) =>
{
if let Ok((next, element)) = package_body_element_fallback(input) {
if next.location_offset() == input.location_offset() {
return Err(nom::Err::Failure(nom::error::Error::new(
input,
nom::error::ErrorKind::Many0,
)));
}
elements.push(element);
input = next;
continue;
}
let (next, _) = recover_body_element(input, PACKAGE_BODY_STARTERS)?;
if next.location_offset() == input.location_offset() {
return Err(nom::Err::Failure(nom::error::Error::new(
input,
nom::error::ErrorKind::Many0,
)));
}
let recovery = build_recovery_error_node_from_span(
input,
next,
PACKAGE_BODY_STARTERS,
"package body",
"recovered_package_body_element",
);
if matches!(
recovery.code.as_str(),
"invalid_typing_operator"
| "missing_body_or_semicolon"
| "missing_expression_after_operator"
| "unexpected_keyword_in_scope"
| "unsupported_annotation_syntax"
) {
elements.push(node_from_to(
input,
next,
PackageBodyElement::Error(Node::new(crate::ast::Span::dummy(), recovery)),
));
input = next;
continue;
}
if let Ok((next, ext)) = map(
extended_library_decl,
PackageBodyElement::ExtendedLibraryDecl,
)
.parse(input)
{
if next.location_offset() == input.location_offset() {
return Err(nom::Err::Failure(nom::error::Error::new(
input,
nom::error::ErrorKind::Many0,
)));
}
elements.push(node_from_to(input, next, ext));
input = next;
continue;
}
elements.push(node_from_to(
input,
next,
PackageBodyElement::Error(Node::new(crate::ast::Span::dummy(), recovery)),
));
input = next;
}
Err(_) => {
if let Ok((next, element)) = package_body_element_fallback(input) {
if next.location_offset() > input.location_offset() {
elements.push(element);
input = next;
continue;
}
}
let (next, _) = recover_body_element(input, PACKAGE_BODY_STARTERS)?;
if next.location_offset() == input.location_offset() {
return Err(nom::Err::Failure(nom::error::Error::new(
input,
nom::error::ErrorKind::Many0,
)));
}
let recovery = build_recovery_error_node_from_span(
input,
next,
PACKAGE_BODY_STARTERS,
"package body",
"recovered_package_body_element",
);
elements.push(node_from_to(
input,
next,
PackageBodyElement::Error(Node::new(crate::ast::Span::dummy(), recovery)),
));
input = next;
}
}
}
}
pub(crate) fn filter_member(input: Input<'_>) -> IResult<Input<'_>, Node<FilterMember>> {
let start = input;
let (input, _) = ws_and_comments(input)?;
let (input, visibility) = opt(alt((
map(preceded(tag(&b"public"[..]), ws1), |_| Visibility::Public),
map(preceded(tag(&b"private"[..]), ws1), |_| Visibility::Private),
map(preceded(tag(&b"protected"[..]), ws1), |_| {
Visibility::Protected
}),
)))
.parse(input)?;
let (input, _) = tag(&b"filter"[..]).parse(input)?;
let (input, _) = ws1(input)?;
let (input, condition) = expression(input)?;
let (input, _) = preceded(ws_and_comments, tag(&b";"[..])).parse(input)?;
Ok((
input,
node_from_to(
start,
input,
FilterMember {
visibility,
condition,
},
),
))
}
macro_rules! try_package_body_dispatch {
($input:expr, $start:expr, $parser:expr, $wrap:expr) => {
if let Ok((input, elem)) = map($parser, $wrap).parse($input) {
return Ok((input, node_from_to($start, input, elem)));
}
};
}
fn try_package_body_annotations<'a>(
input: Input<'a>,
start: Input<'a>,
) -> IResult<Input<'a>, Node<PackageBodyElement>> {
try_package_body_dispatch!(input, start, doc_comment, PackageBodyElement::Doc);
try_package_body_dispatch!(
input,
start,
comment_annotation,
PackageBodyElement::Comment
);
try_package_body_dispatch!(
input,
start,
textual_representation,
PackageBodyElement::TextualRep
);
try_package_body_dispatch!(input, start, filter_member, PackageBodyElement::Filter);
try_package_body_dispatch!(
input,
start,
|i| attribute_def(i, false),
PackageBodyElement::AttributeDef
);
Err(nom::Err::Error(nom::error::Error::new(
input,
nom::error::ErrorKind::Alt,
)))
}
fn try_package_body_packages<'a>(
input: Input<'a>,
start: Input<'a>,
) -> IResult<Input<'a>, Node<PackageBodyElement>> {
try_package_body_dispatch!(
input,
start,
library_package_,
PackageBodyElement::LibraryPackage
);
try_package_body_dispatch!(input, start, package_, PackageBodyElement::Package);
try_package_body_dispatch!(input, start, import_, PackageBodyElement::Import);
Err(nom::Err::Error(nom::error::Error::new(
input,
nom::error::ErrorKind::Alt,
)))
}
fn try_package_body_structure<'a>(
input: Input<'a>,
start: Input<'a>,
) -> IResult<Input<'a>, Node<PackageBodyElement>> {
try_package_body_dispatch!(input, start, part_def_or_usage, |p| match p {
PartDefOrUsage::Def(n) => PackageBodyElement::PartDef(n),
PartDefOrUsage::Usage(n) => PackageBodyElement::PartUsage(n),
});
try_package_body_dispatch!(input, start, port_def, PackageBodyElement::PortDef);
try_package_body_dispatch!(
input,
start,
interface_def,
PackageBodyElement::InterfaceDef
);
try_package_body_dispatch!(
input,
start,
connection_def,
PackageBodyElement::ConnectionDef
);
try_package_body_dispatch!(input, start, dependency, PackageBodyElement::Dependency);
try_package_body_dispatch!(input, start, metadata_def, PackageBodyElement::MetadataDef);
try_package_body_dispatch!(input, start, enum_def, PackageBodyElement::EnumDef);
try_package_body_dispatch!(
input,
start,
occurrence_def,
PackageBodyElement::OccurrenceDef
);
try_package_body_dispatch!(
input,
start,
occurrence_usage,
PackageBodyElement::OccurrenceUsage
);
try_package_body_dispatch!(
input,
start,
individual_usage,
PackageBodyElement::OccurrenceUsage
);
try_package_body_dispatch!(
input,
start,
snapshot_usage,
PackageBodyElement::OccurrenceUsage
);
try_package_body_dispatch!(
input,
start,
timeslice_usage,
PackageBodyElement::OccurrenceUsage
);
try_package_body_dispatch!(
input,
start,
allocation_def,
PackageBodyElement::AllocationDef
);
try_package_body_dispatch!(
input,
start,
allocation_usage,
PackageBodyElement::AllocationUsage
);
try_package_body_dispatch!(
input,
start,
allocate_usage,
PackageBodyElement::AllocationUsage
);
try_package_body_dispatch!(input, start, flow_def, PackageBodyElement::FlowDef);
try_package_body_dispatch!(input, start, flow_usage, PackageBodyElement::FlowUsage);
Err(nom::Err::Error(nom::error::Error::new(
input,
nom::error::ErrorKind::Alt,
)))
}
fn try_package_body_behavior<'a>(
input: Input<'a>,
start: Input<'a>,
) -> IResult<Input<'a>, Node<PackageBodyElement>> {
try_package_body_dispatch!(input, start, alias_def, PackageBodyElement::AliasDef);
try_package_body_dispatch!(input, start, action_def, PackageBodyElement::ActionDef);
try_package_body_dispatch!(input, start, action_usage, PackageBodyElement::ActionUsage);
try_package_body_dispatch!(input, start, state_def, PackageBodyElement::StateDef);
try_package_body_dispatch!(input, start, state_usage, PackageBodyElement::StateUsage);
try_package_body_dispatch!(input, start, item_def, PackageBodyElement::ItemDef);
try_package_body_dispatch!(
input,
start,
individual_def,
PackageBodyElement::IndividualDef
);
try_package_body_dispatch!(
input,
start,
constraint_def,
PackageBodyElement::ConstraintDef
);
try_package_body_dispatch!(input, start, calc_def, PackageBodyElement::CalcDef);
Err(nom::Err::Error(nom::error::Error::new(
input,
nom::error::ErrorKind::Alt,
)))
}
fn try_package_body_requirement<'a>(
input: Input<'a>,
start: Input<'a>,
) -> IResult<Input<'a>, Node<PackageBodyElement>> {
try_package_body_dispatch!(
input,
start,
requirement_def,
PackageBodyElement::RequirementDef
);
try_package_body_dispatch!(
input,
start,
requirement_usage,
PackageBodyElement::RequirementUsage
);
try_package_body_dispatch!(input, start, satisfy, PackageBodyElement::Satisfy);
try_package_body_dispatch!(input, start, use_case_def, PackageBodyElement::UseCaseDef);
try_package_body_dispatch!(
input,
start,
use_case_usage,
PackageBodyElement::UseCaseUsage
);
try_package_body_dispatch!(input, start, case_def, PackageBodyElement::CaseDef);
try_package_body_dispatch!(input, start, case_usage, PackageBodyElement::CaseUsage);
try_package_body_dispatch!(
input,
start,
analysis_case_def,
PackageBodyElement::AnalysisCaseDef
);
try_package_body_dispatch!(
input,
start,
analysis_case_usage,
PackageBodyElement::AnalysisCaseUsage
);
try_package_body_dispatch!(
input,
start,
verification_case_def,
PackageBodyElement::VerificationCaseDef
);
try_package_body_dispatch!(
input,
start,
verification_case_usage,
PackageBodyElement::VerificationCaseUsage
);
try_package_body_dispatch!(
input,
start,
concern_usage,
PackageBodyElement::ConcernUsage
);
try_package_body_dispatch!(input, start, actor_decl, PackageBodyElement::Actor);
Err(nom::Err::Error(nom::error::Error::new(
input,
nom::error::ErrorKind::Alt,
)))
}
fn try_package_body_view<'a>(
input: Input<'a>,
start: Input<'a>,
) -> IResult<Input<'a>, Node<PackageBodyElement>> {
try_package_body_dispatch!(input, start, view_def, PackageBodyElement::ViewDef);
try_package_body_dispatch!(
input,
start,
viewpoint_def,
PackageBodyElement::ViewpointDef
);
try_package_body_dispatch!(
input,
start,
rendering_def,
PackageBodyElement::RenderingDef
);
try_package_body_dispatch!(input, start, view_usage, PackageBodyElement::ViewUsage);
try_package_body_dispatch!(
input,
start,
viewpoint_usage,
PackageBodyElement::ViewpointUsage
);
try_package_body_dispatch!(
input,
start,
rendering_usage,
PackageBodyElement::RenderingUsage
);
try_package_body_dispatch!(input, start, feature_decl, PackageBodyElement::FeatureDecl);
try_package_body_dispatch!(
input,
start,
classifier_decl,
PackageBodyElement::ClassifierDecl
);
try_package_body_dispatch!(
input,
start,
kerml_semantic_decl,
PackageBodyElement::KermlSemanticDecl
);
Err(nom::Err::Error(nom::error::Error::new(
input,
nom::error::ErrorKind::Alt,
)))
}
pub(crate) fn package_body_element(
input: Input<'_>,
) -> IResult<Input<'_>, Node<PackageBodyElement>> {
let (input, _) = ws_and_comments(input)?;
let start = input;
if let Ok(r) = try_package_body_annotations(input, start) {
return Ok(r);
}
if let Ok(r) = try_package_body_packages(input, start) {
return Ok(r);
}
if let Ok(r) = try_package_body_structure(input, start) {
return Ok(r);
}
if let Ok(r) = try_package_body_behavior(input, start) {
return Ok(r);
}
if let Ok(r) = try_package_body_requirement(input, start) {
return Ok(r);
}
if let Ok(r) = try_package_body_view(input, start) {
return Ok(r);
}
if starts_with_keyword(input.fragment(), b"occurrence") {
if let Ok((next, _)) = recover_body_element(input, PACKAGE_BODY_STARTERS) {
if next.location_offset() != input.location_offset() {
let recovery = build_recovery_error_node_from_span(
input,
next,
PACKAGE_BODY_STARTERS,
"package body",
"recovered_package_body_element",
);
if matches!(
recovery.code.as_str(),
"invalid_typing_operator" | "missing_type_reference"
) {
return Err(nom::Err::Error(nom::error::Error::new(
input,
nom::error::ErrorKind::Tag,
)));
}
}
}
}
if let Ok((input, elem)) =
map(kerml_feature_decl, PackageBodyElement::KermlFeatureDecl).parse(input)
{
return Ok((input, node_from_to(start, input, elem)));
}
if let Ok((next, _)) = recover_body_element(input, PACKAGE_BODY_STARTERS) {
if next.location_offset() != input.location_offset() {
let recovery = build_recovery_error_node_from_span(
input,
next,
PACKAGE_BODY_STARTERS,
"package body",
"recovered_package_body_element",
);
if matches!(
recovery.code.as_str(),
"invalid_typing_operator"
| "missing_body_or_semicolon"
| "missing_expression_after_operator"
| "unexpected_keyword_in_scope"
| "unsupported_annotation_syntax"
) {
return Err(nom::Err::Error(nom::error::Error::new(
input,
nom::error::ErrorKind::Tag,
)));
}
}
}
let (input, elem) = map(
extended_library_decl,
PackageBodyElement::ExtendedLibraryDecl,
)
.parse(input)?;
Ok((input, node_from_to(start, input, elem)))
}
pub(crate) fn root_namespace(input: Input<'_>) -> IResult<Input<'_>, RootNamespace> {
let (input, _) = ws_and_comments(input)?;
let (input, elements) = many0(preceded(ws_and_comments, root_element)).parse(input)?;
let (input, _) = ws_and_comments(input)?;
Ok((input, RootNamespace { elements }))
}
#[cfg(test)]
mod tests {
use super::*;
use nom_locate::LocatedSpan;
use std::path::PathBuf;
fn sysml_v2_release_root() -> PathBuf {
std::env::var_os("SYSML_V2_RELEASE_DIR")
.map(PathBuf::from)
.unwrap_or_else(|| PathBuf::from(env!("CARGO_MANIFEST_DIR")).join("sysml-v2-release"))
}
fn primitive_data_types_fixture() -> Option<String> {
let path = sysml_v2_release_root()
.join("sysml")
.join("src")
.join("validation")
.join("15-Properties-Values-Expressions")
.join("15_10-Primitive Data Types.sysml");
std::fs::read_to_string(path).ok()
}
#[test]
fn kitchen_timer_display_tail_parses_as_package_body_element() {
let input = include_str!("../../tests/fixtures/KitchenTimer.sysml")
.replace("\r\n", "\n")
.replace('\r', "\n");
let start = input
.find("\tpart def Display {")
.expect("fixture should contain Display part");
let tail = &input.as_bytes()[start..];
let located = LocatedSpan::new(tail);
let result = package_body_element(located);
assert!(
result.is_ok(),
"package_body_element should parse Display tail, got {:?}",
result
);
}
#[test]
fn kitchen_timer_display_tail_parses_as_part_directly() {
let input = include_str!("../../tests/fixtures/KitchenTimer.sysml")
.replace("\r\n", "\n")
.replace('\r', "\n");
let start = input
.find("\tpart def Display {")
.expect("fixture should contain Display part");
let tail = &input.as_bytes()[start..];
let located = LocatedSpan::new(tail);
let (located, _) = ws_and_comments(located).expect("leading ws");
let result = part_def_or_usage(located);
assert!(
result.is_ok(),
"part_def_or_usage should parse Display tail directly, got {:?}",
result
);
}
#[test]
fn primitive_data_types_validation_fixture_package_parses_directly() {
let Some(input) = primitive_data_types_fixture() else {
return;
};
let located = LocatedSpan::new(input.as_bytes());
let result = package_(located);
assert!(
result.is_ok(),
"package_ should parse fixture, got {:?}",
result
);
}
#[test]
fn primitive_data_types_validation_fixture_package_body_parses_directly() {
let Some(input) = primitive_data_types_fixture() else {
return;
};
let start = input
.find('{')
.expect("fixture should contain package body");
let located = LocatedSpan::new(&input.as_bytes()[start..]);
let result = package_body_brace(located);
assert!(
result.is_ok(),
"package_body_brace should parse fixture body, got {:?}",
result
);
}
}