diff --git a/plantuml/parser/integration_test/sequence_diagram/comprehensive_sequence_test.puml b/plantuml/parser/integration_test/sequence_diagram/comprehensive_sequence_test.puml
index d9f680f2..23243e06 100644
--- a/plantuml/parser/integration_test/sequence_diagram/comprehensive_sequence_test.puml
+++ b/plantuml/parser/integration_test/sequence_diagram/comprehensive_sequence_test.puml
@@ -339,7 +339,7 @@ group Extended Patterns: Lost and Found Messages
' Lost/found with brackets on different sides
[o-> Actor1 : Right bracket on left
- Actor1 -->o[ : Left bracket on right
+ Actor1 -->o] : Left bracket on right
end group
group Extended Patterns: Activation Markers
@@ -364,7 +364,7 @@ group Extended Patterns: Ref Blocks
ref over Builder
Another reference block
- end
+ end ref
end group
group Extended Patterns: Dividers with URLs
diff --git a/plantuml/parser/puml_parser/src/grammar/common.pest b/plantuml/parser/puml_parser/src/grammar/common.pest
index 7ca0de08..e9f54acb 100644
--- a/plantuml/parser/puml_parser/src/grammar/common.pest
+++ b/plantuml/parser/puml_parser/src/grammar/common.pest
@@ -84,23 +84,33 @@ COMMENT = _{
}
////////////////////////////////////////////////////////////////////////////////
-// Ignored Statements
+// Ignored Blocks and Statements
//
// Non-structural PlantUML directives such as title, style, layout, and scaling.
// Parsed for completeness but generally skipped in AST construction.
//
// Examples:
+//
// title System Overview
// skinparam shadowing false
// left to right direction
// scale 1.2
////////////////////////////////////////////////////////////////////////////////
+ignored_block = _{
+ style_block
+ | note_multiline
+}
+
+style_block = { "" ~ EOL? }
+
ignored_stmt = _{
direction_stmt
| skinparam_stmt
| title_stmt
+ | note_single_line
| scale_stmt
- | namespace_separator_stmt
+ | namespace_separator_stmt
}
skinparam_stmt = @{ ^"skinparam" ~ LINE_REST }
diff --git a/plantuml/parser/puml_parser/src/grammar/sequence.pest b/plantuml/parser/puml_parser/src/grammar/sequence.pest
index 4d59c2cf..c3eb1fc0 100644
--- a/plantuml/parser/puml_parser/src/grammar/sequence.pest
+++ b/plantuml/parser/puml_parser/src/grammar/sequence.pest
@@ -13,106 +13,30 @@
// PlantUML Sequence Diagram Grammar for Pest Parser
-sequence_start = { empty_line* ~ startuml ~ (note_multiline | ref_multiline | sequence_statement | empty_line)* ~ enduml }
-
-sequence_statement = {
- (
- note_declaration |
- pragma | ignored_stmt |
- minwidth | rotate | transformation |
- hide_unlinked | show_unlinked |
- sprite_inline | sprite_block_start |
- hide_show_member | hide_show_stereotype |
- participant_def |
- activate_cmd | deactivate_cmd | destroy_cmd | create_cmd |
- box_start | box_end |
- group_cmd |
- divider | delay |
- return_cmd |
- ref_inline | ref_block_start |
- skin | autonumber | autonumber_stop | autonumber_resume | autonumber_inc |
- autoactivate | footbox_cmd | ellipsis |
- function_def | function_return | function_end |
- message |
- activation_short
- ) ~ EOL
-}
-
-// Basic elements
-pragma = { ^"!pragma" ~ identifier ~ pragma_value? }
-pragma_value = { (!EOL ~ ANY)+ }
-
-// Function definitions (preprocessor functions)
-function_def = { ^"!function" ~ function_content }
-function_return = { ^"!return" ~ function_content }
-function_end = { ^"!endfunction" }
-function_content = { (!EOL ~ ANY)+ }
-
-// Multi-line blocks
-
-legend_block_start = { ^"legend" ~ legend_pos? ~ legend_align? }
-legend_pos = { ^"top" | ^"bottom" }
-legend_align = { ^"left" | ^"right" | ^"center" }
-legend_block_end = { ^"end" ~ WHITESPACE? ~ ^"legend" }
-
-// rotate, etc.
-minwidth = { ^"minwidth" ~ NUMBER }
-rotate = { ^"rotate" }
-
-
-transformation = { ^"!transformation" ~ transformation_value }
-transformation_value = { (!("{" | EOL) ~ ANY)+ }
-transformation_block_start = { ^"!transformation" ~ "{" }
-transformation_block_end = { "!" ~ "}" }
-
-hide_unlinked = { ^"hide" ~ ^"unlinked" }
-show_unlinked = { ^"show" ~ ^"unlinked" }
-
-// Sprite
-sprite_block_start = {
- ^"sprite" ~ sprite_name ~ sprite_dimensions? ~ "{"
-}
-sprite_block_end = { (^"end" ~ WHITESPACE? ~ ^"sprite") | "}" }
-sprite_inline = {
- ^"sprite" ~ sprite_name ~ sprite_dimensions? ~ (sprite_encoding | sprite_data)
+sequence_start = {
+ empty_line* ~ startuml
+ ~ (ignored_block | sequence_ignored_block | ignored_stmt | sequence_ignored_stmt | sequence_line | empty_line)*
+ ~ enduml
}
-sprite_name = { "$"? ~ identifier }
-sprite_dimensions = {
- "[" ~ NUMBER ~ "x" ~ NUMBER ~ "/" ~
- (NUMBER ~ "z"? | ^"color") ~
- "]"
-}
-sprite_encoding = { ASCII_ALPHANUMERIC+ }
-sprite_data = { ANY+ }
-// Hide/Show
-hide_show_member = {
- hide_or_show ~ visibility_list ~ member_type
-}
-hide_show_stereotype = {
- hide_or_show ~ stereotype_target* ~ empty_kw? ~ stereotype_elem
-}
-hide_or_show = { ^"hide" | ^"show" }
-visibility_list = { sequence_visibility ~ ("," ~ sequence_visibility)* }
-sequence_visibility = { ^"public" | ^"private" | ^"protected" | ^"package" }
-member_type = { ^"members" | ^"member" | ^"attributes" | ^"attribute" | ^"fields" | ^"field" | ^"methods" | ^"method" }
-stereotype_target = { class_type | sequence_qualified_name | quoted_string | stereotype }
-class_type = { ^"class" | ^"object" | ^"interface" | ^"enum" | ^"annotation" | ^"abstract" }
-empty_kw = { ^"empty" }
-stereotype_elem = {
- ^"members" | ^"member" | ^"attributes" | ^"attribute" | ^"fields" | ^"field" |
- ^"methods" | ^"method" | ^"circle" ~ ASCII_ALPHANUMERIC* | ^"stereotypes" | ^"stereotype"
+sequence_line = _{ sequence_statement ~ EOL }
+
+sequence_statement = {
+ participant_def
+ | message
+ | lifecycle_cmd
+ | group_cmd
+ | return_cmd
+ | ref_stmt
}
// Participant definitions
participant_def = {
- create_kw?
- ~ participant_type
+ participant_type
~ participant_identifier
~ stereotype? ~ order_clause? ~ color_spec?
}
-create_kw = { ^"create" }
participant_type = @{
(^"participant" | ^"collections" | ^"database" | ^"boundary" | ^"control" |
^"entity" | ^"queue" | ^"actor") ~ &(" " | "\t" | "\n" | "\r")
@@ -135,25 +59,36 @@ display_with_alias = { NAME ~ ^"as" ~ NAME }
// participant Alias as "Display"
alias_with_quoted_display = { NAME ~ ^"as" ~ quoted_string }
-// participant "Display"
quoted_display = { quoted_string }
-
-// participant Alias
alias_only = { NAME }
color_spec = @{ BASIC_COLOR }
order_clause = { ^"order" ~ signed_number }
signed_number = { "-"? ~ NUMBER }
-// Activate/Deactivate/Destroy/Create commands
-activate_cmd = { ^"activate" ~ participant_ref }
+// Lifecycle commands
+lifecycle_cmd = {
+ create_cmd
+ | activate_cmd
+ | deactivate_cmd
+ | destroy_cmd
+ | activation_short
+}
+
+create_cmd = {
+ ^"create"
+ ~ participant_type?
+ ~ participant_identifier
+ ~ stereotype? ~ order_clause? ~ color_spec?
+}
+activate_cmd = { ^"activate" ~ participant_ref ~ color_spec? }
deactivate_cmd = { ^"deactivate" ~ participant_ref? }
destroy_cmd = { ^"destroy" ~ participant_ref }
-create_cmd = { ^"create" ~ participant_ref }
+activation_short = { participant_ref ~ (activate_suffix | deactivate_suffix) }
-// Sequence Arrow
-SEQUENCE_ARROW_PREFIX_CHAR = _{ "<" | "/" | "\\" | "|" | "*" | "(" | ")" | "#" | "^" | "@" }
-SEQUENCE_ARROW_SUFFIX_CHAR = _{ ">" | "/" | "\\" | "|" | "*" | "(" | ")" | "#" | "^" | "@" }
+// Sequence arrows
+SEQUENCE_ARROW_PREFIX_CHAR = _{ "<" | "/" | "\\" | "|" | "*" | "(" | ")" | "#" | "^" | "@" | "o" | "x" }
+SEQUENCE_ARROW_SUFFIX_CHAR = _{ ">" | "/" | "\\" | "|" | "*" | "(" | ")" | "#" | "^" | "@" | "o" | "x" }
sequence_arrow_prefix = @{ SEQUENCE_ARROW_PREFIX_CHAR+ }
sequence_arrow_suffix = @{ SEQUENCE_ARROW_SUFFIX_CHAR+ }
@@ -168,9 +103,21 @@ sequence_arrow = {
// Messages
message = {
- parallel_marker? ~
- message_endpoint ~ sequence_arrow ~ message_endpoint ~
- message_suffix? ~ sequence_description?
+ parallel_marker? ~ message_body ~ message_suffix? ~ sequence_description?
+}
+message_body = {
+ message_full
+ | message_missing_left
+ | message_missing_right
+}
+message_full = {
+ message_endpoint ~ sequence_arrow ~ message_endpoint
+}
+message_missing_left = {
+ sequence_arrow ~ message_endpoint
+}
+message_missing_right = {
+ message_endpoint ~ !deactivate_suffix ~ sequence_arrow
}
parallel_marker = { "&" }
@@ -189,102 +136,236 @@ inline_participant = {
lost_found_marker = {
left_lost_found
| right_lost_found
+ | short_lost_found
}
-// '[', '[o', '[x'
-left_lost_found = { "[" ~ ("o" | "x")? }
-// ']', 'o]', 'x]'
-right_lost_found = { ("o" | "x")? ~ "]" }
+left_lost_found = { "[" }
+right_lost_found = { "]" }
+short_lost_found = { "?" }
message_suffix = {
- activate_suffix
- | deactivate_suffix
- | create_suffix
- | destroy_suffix
+ message_lifecycle_suffix+
}
+message_lifecycle_suffix = _{ activate_suffix | deactivate_suffix | create_suffix | destroy_suffix }
activate_suffix = { "++" ~ color_spec? }
deactivate_suffix = { "--" }
create_suffix = { "**" }
destroy_suffix = { "!!" }
-// Short activation syntax
-activation_short = { participant_ref ~ ("++" | "--") }
-participant_ref = { CNAME | quoted_string }
-
// Return
-return_cmd = { parallel_marker? ~ ^"return" ~ sequence_text_content? }
-
-// Box
-box_start = { "box" ~ quoted_string? ~ color_spec? }
-box_end = { ^"end" ~ WHITESPACE? ~ ^"box" }
+return_cmd = { ^"return" ~ sequence_text_content? }
// Group commands (alt, opt, loop, etc.)
group_cmd = {
- parallel_marker? ~ group_type ~ group_condition?
+ group_start
+ | group_branch
+ | group_end
}
-
-group_condition = @{ (!EOL ~ ANY)+ }
-
-group_type = @{
- (^"critical" | ^"par2" | ^"par" | ^"opt" | ^"alt" | ^"loop" | ^"break" |
- ^"else" | ^"also" | ^"group" | ^"end") ~ &(" " | "\t" | "\n" | "\r")
+group_start = {
+ group_start_type ~ group_label?
}
-
-// Divider and delay
-divider = { "==" ~ divider_text? ~ "==" }
-divider_text = @{ (!"==" ~ !EOL ~ ANY)+ }
-delay = { ("||" ~ NUMBER? ~ "|"+) | ("…" ~ sequence_text_content? ~ "…") | ("..." ~ sequence_text_content? ~ "...") }
-ellipsis = { ("..." ~ sequence_text_content? ~ "...") | ("…" ~ sequence_text_content? ~ "…") | "..." | "…" }
+group_branch = {
+ group_branch_type ~ group_label?
+}
+group_end = { ^"end" ~ group_start_type? }
+group_start_type = @{
+ ^"alt"
+ | ^"opt"
+ | ^"loop"
+ | ^"par"
+ | ^"break"
+ | ^"critical"
+ | ^"group"
+}
+group_branch_type = @{ ^"else" }
+group_label = @{ (!EOL ~ ANY)+ }
// Reference
+ref_stmt = {
+ ref_inline
+ | ref_block
+}
ref_inline = {
^"ref" ~ ^"over" ~ participant_list ~ ":" ~ sequence_text_content
}
-
-ref_multiline = @{
- ^"ref" ~
- (!NEWLINE ~ ANY)* ~ // rest of header line ("over participant")
- NEWLINE ~
+ref_block = {
+ ^"ref" ~ ^"over" ~ participant_list ~ EOL
+ ~ ref_body
+ ~ ref_end
+}
+ref_body = {
(
- !(WHITESPACE* ~ (^"endref" | (^"end" ~ WHITESPACE ~ ^"ref") | (^"end" ~ WHITESPACE* ~ NEWLINE))) ~
- (!NEWLINE ~ ANY)* ~
- NEWLINE
- )* ~
- WHITESPACE* ~ (^"endref" | (^"end" ~ WHITESPACE ~ ^"ref") | (^"end" ~ WHITESPACE* ~ NEWLINE)) ~
- EOL?
+ !(WHITESPACE* ~ ref_end)
+ ~ ANY
+ )*
+}
+ref_end = { ^"end" ~ ^"ref" }
+
+participant_list = { participant_ref ~ ("," ~ participant_ref)* }
+participant_ref = { CNAME }
+
+sequence_qualified_name = @{ identifier ~ ("." ~ identifier)* }
+
+sequence_text_content = { (!EOL ~ ANY)+ }
+sequence_description = { ":" ~ sequence_text_content? }
+
+// Ignored blocks
+sequence_ignored_block = _{
+ sequence_sprite_block
+ | sequence_legend_block
+ | sequence_transformation_block
+}
+
+sequence_legend_block = {
+ legend_block_start ~ EOL
+ ~ (!legend_block_end ~ ANY)*
+ ~ legend_block_end ~ EOL?
+}
+legend_block_start = { ^"legend" ~ legend_pos? ~ legend_align? }
+legend_pos = { ^"top" | ^"bottom" }
+legend_align = { ^"left" | ^"right" | ^"center" }
+legend_block_end = { ^"end" ~ WHITESPACE? ~ ^"legend" }
+
+sequence_transformation_block = {
+ transformation_block_start ~ EOL?
+ ~ (!transformation_block_end ~ ANY)*
+ ~ transformation_block_end ~ EOL?
}
+transformation_block_start = { ^"!transformation" ~ "{" }
+transformation_block_end = { "!" ~ "}" }
-ref_block_start = {
- ^"ref" ~ ^"over" ~ participant_list
+sequence_sprite_block = {
+ sprite_block_start ~ EOL?
+ ~ (!sprite_block_end ~ ANY)*
+ ~ sprite_block_end ~ EOL?
}
+sprite_block_start = { ^"sprite" ~ sprite_name ~ sprite_dimensions? ~ "{" }
+sprite_block_end = { (^"end" ~ WHITESPACE? ~ ^"sprite") | "}" }
-ref_block_end = { ^"end" ~ WHITESPACE? ~ (^"ref")? }
+// Ignored statements
+sequence_ignored_stmt = _{
+ sequence_preprocessor_stmt
+ | sequence_layout_stmt
+ | sequence_visibility_stmt
+ | sequence_rendering_stmt
+ | sequence_sprite_stmt
+}
-participant_list = { participant_ref ~ ("," ~ participant_ref)* }
+sequence_preprocessor_stmt = _{
+ function_def
+ | function_return
+ | function_end
+ | pragma
+}
+
+sequence_layout_stmt = _{
+ minwidth
+ | rotate
+ | box_cmd
+ | divider
+ | delay
+ | ellipsis
+}
+
+sequence_visibility_stmt = _{
+ hide_unlinked
+ | show_unlinked
+ | hide_show_member
+ | hide_show_stereotype
+}
+
+sequence_rendering_stmt = _{
+ transformation
+ | skin
+ | autonumber_stop
+ | autonumber_resume
+ | autonumber_inc
+ | autonumber
+ | autoactivate
+ | footbox_cmd
+}
+
+sequence_sprite_stmt = _{
+ sprite_inline
+}
+
+pragma = { ^"!pragma" ~ identifier ~ pragma_value? }
+pragma_value = { (!EOL ~ ANY)+ }
+
+minwidth = { ^"minwidth" ~ NUMBER }
+rotate = { ^"rotate" }
+
+transformation = { ^"!transformation" ~ transformation_value }
+transformation_value = { (!("{" | EOL) ~ ANY)+ }
+
+hide_unlinked = { ^"hide" ~ ^"unlinked" }
+show_unlinked = { ^"show" ~ ^"unlinked" }
+
+sprite_inline = {
+ ^"sprite" ~ sprite_name ~ sprite_dimensions? ~ (sprite_encoding | sprite_data)
+}
+sprite_name = { "$"? ~ identifier }
+sprite_dimensions = {
+ "[" ~ NUMBER ~ "x" ~ NUMBER ~ "/" ~
+ (NUMBER ~ "z"? | ^"color") ~
+ "]"
+}
+sprite_encoding = { ASCII_ALPHANUMERIC+ }
+sprite_data = { ANY+ }
+
+hide_show_member = {
+ hide_or_show ~ visibility_list ~ member_type
+}
+hide_show_stereotype = {
+ hide_or_show ~ stereotype_target* ~ empty_kw? ~ stereotype_elem
+}
+hide_or_show = { ^"hide" | ^"show" }
+visibility_list = { sequence_visibility ~ ("," ~ sequence_visibility)* }
+sequence_visibility = { ^"public" | ^"private" | ^"protected" | ^"package" }
+member_type = { ^"members" | ^"member" | ^"attributes" | ^"attribute" | ^"fields" | ^"field" | ^"methods" | ^"method" }
+stereotype_target = { class_type | sequence_qualified_name | quoted_string | stereotype }
+class_type = { ^"class" | ^"object" | ^"interface" | ^"enum" | ^"annotation" | ^"abstract" }
+empty_kw = { ^"empty" }
+stereotype_elem = {
+ ^"members" | ^"member" | ^"attributes" | ^"attribute" | ^"fields" | ^"field" |
+ ^"methods" | ^"method" | ^"circle" ~ ASCII_ALPHANUMERIC* | ^"stereotypes" | ^"stereotype"
+}
+
+box_cmd = {
+ box_start
+ | box_end
+}
+box_start = { "box" ~ quoted_string? ~ color_spec? }
+box_end = { ^"end" ~ ^"box" }
+
+divider = { "==" ~ divider_text? ~ "==" }
+divider_text = @{ (!"==" ~ !EOL ~ ANY)+ }
+delay = {
+ "||" ~ NUMBER? ~ "|"+
+}
+ellipsis = {
+ "..."
+ | ("..." ~ sequence_text_content? ~ "...")
+}
-// Skin
skin = { ^"skin" ~ sequence_qualified_name }
-// Autonumber
autonumber = {
^"autonumber" ~ autonumber_format? ~ autonumber_start? ~ autonumber_step?
}
autonumber_format = { (ASCII_DIGIT ~ (!(WHITESPACE | EOL) ~ ANY)* ~ ASCII_DIGIT) | ASCII_DIGIT }
autonumber_start = { NUMBER }
autonumber_step = { quoted_string }
-
autonumber_stop = { ^"autonumber" ~ ^"stop" }
autonumber_resume = { ^"autonumber" ~ ^"resume" ~ NUMBER? ~ quoted_string? }
autonumber_inc = { ^"autonumber" ~ ^"inc" ~ (ASCII_ALPHA)? }
-// Autoactivate
autoactivate = { ^"autoactivate" ~ autoactivate_state? }
autoactivate_state = { ^"on" | ^"off" }
-// Footbox
footbox_cmd = { hide_or_show? ~ ^"footbox" ~ footbox_state? }
footbox_state = { ^"on" | ^"off" }
-sequence_qualified_name = @{ identifier ~ ("." ~ identifier)* }
-
-sequence_text_content = { (!EOL ~ ANY)+ }
-sequence_description = { ":" ~ sequence_text_content? }
+function_def = { ^"!function" ~ function_content }
+function_return = { ^"!return" ~ function_content }
+function_end = { ^"!endfunction" }
+function_content = { (!EOL ~ ANY)+ }
diff --git a/plantuml/parser/puml_parser/src/sequence_diagram/src/lib.rs b/plantuml/parser/puml_parser/src/sequence_diagram/src/lib.rs
index 58bcda12..85343bc5 100644
--- a/plantuml/parser/puml_parser/src/sequence_diagram/src/lib.rs
+++ b/plantuml/parser/puml_parser/src/sequence_diagram/src/lib.rs
@@ -15,9 +15,9 @@ pub mod sequence_ast;
mod sequence_parser;
pub use sequence_ast::{
- ActivateCmd, Arrow, CreateCmd, DeactivateCmd, DestroyCmd, GroupCmd, GroupType, Message,
- MessageEndpoint, MessageSuffix, ParticipantIdentifier, ParticipantType, SeqPumlDocument,
- Statement,
+ ActivateCmd, Arrow, CreateCmd, DeactivateCmd, DestroyCmd, GroupCmd, GroupElse, GroupEnd,
+ GroupKind, GroupStart, Message, MessageEndpoint, MessageSuffix, ParticipantIdentifier,
+ ParticipantRef, ParticipantType, RefCmd, ReturnCmd, SeqPumlDocument, Statement,
};
pub use sequence_parser::{PumlSequenceParser, SequenceError};
diff --git a/plantuml/parser/puml_parser/src/sequence_diagram/src/sequence_ast.rs b/plantuml/parser/puml_parser/src/sequence_diagram/src/sequence_ast.rs
index 099233f7..4832b736 100644
--- a/plantuml/parser/puml_parser/src/sequence_diagram/src/sequence_ast.rs
+++ b/plantuml/parser/puml_parser/src/sequence_diagram/src/sequence_ast.rs
@@ -30,20 +30,28 @@ pub struct SeqPumlDocument {
// expected to be revisited as the sequence parser/resolver model settles.
#[allow(clippy::large_enum_variant)]
pub enum Statement {
- DestroyCmd(DestroyCmd),
CreateCmd(CreateCmd),
+ DestroyCmd(DestroyCmd),
ActivateCmd(ActivateCmd),
DeactivateCmd(DeactivateCmd),
ParticipantDef(ParticipantDef),
Message(Message),
GroupCmd(GroupCmd),
+ RefCmd(RefCmd),
+ ReturnCmd(ReturnCmd),
}
// Participant definitions
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
pub struct ParticipantDef {
- #[serde(default)]
- pub is_create: bool,
+ pub participant_type: ParticipantType,
+ pub identifier: ParticipantIdentifier,
+ pub stereotype: Option,
+ pub source_location: SourceLocation,
+}
+
+#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
+pub struct CreateCmd {
pub participant_type: ParticipantType,
pub identifier: ParticipantIdentifier,
pub stereotype: Option,
@@ -68,26 +76,31 @@ pub struct ParticipantIdentifier {
pub alias: Option,
}
-// Destroy/Create commands
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
-pub struct DestroyCmd {
- pub participant: String,
+pub struct ParticipantRef {
+ pub identifier: String,
}
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
-pub struct CreateCmd {
- pub participant: String,
+pub struct DestroyCmd {
+ pub participant: ParticipantRef,
}
-// Activate/Deactivate commands
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
pub struct ActivateCmd {
- pub participant: String,
+ pub participant: ParticipantRef,
}
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
pub struct DeactivateCmd {
- pub participant: Option,
+ pub participant: ParticipantRef,
+}
+
+#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
+pub struct RefCmd {
+ pub participants: Vec,
+ pub text: Option,
+ pub source_location: SourceLocation,
}
// Messages (internal parsing structure)
@@ -113,33 +126,53 @@ pub enum MessageSuffix {
Deactivate, // --
Create, // **
Destroy, // !!
+ Combined(Vec),
}
+// Group commands (alt, opt, loop, etc.) - internal parsing structure
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
-pub enum ActivationType {
- Activate, // ++
- Deactivate, // --
+pub enum GroupCmd {
+ Start(GroupStart),
+ Else(GroupElse),
+ End(GroupEnd),
}
-// Group commands (alt, opt, loop, etc.) - internal parsing structure
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
-pub struct GroupCmd {
- pub group_type: GroupType,
- pub text: Option,
+pub struct GroupStart {
+ pub kind: GroupKind,
+ pub label: Option,
+ /// alt success
+ pub is_parallel: bool,
+ /// "& alt ..."
pub source_location: SourceLocation,
}
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
-pub enum GroupType {
- Opt,
+pub struct GroupElse {
+ pub label: Option,
+ /// else success
+ pub source_location: SourceLocation,
+}
+
+#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
+pub struct GroupEnd {
+ pub kind: Option,
+ pub source_location: SourceLocation,
+}
+
+#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
+pub enum GroupKind {
Alt,
+ Opt,
Loop,
Par,
- Par2,
Break,
Critical,
- Else,
- Also,
- End,
Group,
}
+
+#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
+pub struct ReturnCmd {
+ pub label: Option,
+ pub source_location: SourceLocation,
+}
diff --git a/plantuml/parser/puml_parser/src/sequence_diagram/src/sequence_parser.rs b/plantuml/parser/puml_parser/src/sequence_diagram/src/sequence_parser.rs
index 21487db0..0459d4c1 100644
--- a/plantuml/parser/puml_parser/src/sequence_diagram/src/sequence_parser.rs
+++ b/plantuml/parser/puml_parser/src/sequence_diagram/src/sequence_parser.rs
@@ -67,10 +67,12 @@ impl PumlSequenceParser {
.into_inner()
.next()
.ok_or_else(|| SequenceError::InvalidStatement("empty statement".to_string()))?;
+
match inner.as_rule() {
Rule::participant_def => Ok(vec![Statement::ParticipantDef(
Self::parse_participant_def(inner, source_location)?,
)]),
+ Rule::lifecycle_cmd => Self::parse_lifecycle_cmd(inner, source_location),
Rule::message => Ok(vec![Statement::Message(Self::parse_message(
inner,
source_location,
@@ -79,16 +81,44 @@ impl PumlSequenceParser {
inner,
source_location,
)?)]),
+ Rule::ref_stmt => Ok(vec![Statement::RefCmd(Self::parse_ref_cmd(
+ inner,
+ source_location,
+ ))]),
+ Rule::return_cmd => Ok(vec![Statement::ReturnCmd(Self::parse_return_cmd(
+ inner,
+ source_location,
+ ))]),
+ // Grammar-valid directives that are intentionally not modeled as statements
+ _ => Ok(vec![]),
+ }
+ }
+
+ fn parse_lifecycle_cmd(
+ pair: pest::iterators::Pair,
+ source_location: SourceLocation,
+ ) -> Result, SequenceError> {
+ let inner = pair.into_inner().next().ok_or_else(|| {
+ SequenceError::InvalidStatement("empty lifecycle command".to_string())
+ })?;
+
+ match inner.as_rule() {
+ Rule::create_cmd => Ok(vec![Statement::CreateCmd(Self::parse_create_cmd(
+ inner,
+ source_location,
+ )?)]),
Rule::destroy_cmd => Ok(vec![Statement::DestroyCmd(Self::parse_destroy_cmd(inner)?)]),
- Rule::create_cmd => Ok(vec![Statement::CreateCmd(Self::parse_create_cmd(inner)?)]),
Rule::activate_cmd => Ok(vec![Statement::ActivateCmd(Self::parse_activate_cmd(
inner,
)?)]),
Rule::deactivate_cmd => Ok(vec![Statement::DeactivateCmd(Self::parse_deactivate_cmd(
inner,
)?)]),
- // Grammar-valid directives that are intentionally not modeled as statements
- _ => Ok(vec![]),
+ Rule::activation_short => Self::parse_activation_short(inner),
+ _ => Err(SequenceError::InvalidStatement(format!(
+ "unsupported lifecycle command: {:?}",
+ inner.as_rule()
+ ))),
}
}
@@ -96,16 +126,12 @@ impl PumlSequenceParser {
pair: pest::iterators::Pair,
source_location: SourceLocation,
) -> Result {
- let mut is_create = false;
let mut participant_type: Option = None;
let mut identifier: Option = None;
let mut stereotype: Option = None;
for inner in pair.into_inner() {
match inner.as_rule() {
- Rule::create_kw => {
- is_create = true;
- }
Rule::participant_type => {
participant_type = Some(Self::parse_participant_type(inner)?);
}
@@ -123,7 +149,6 @@ impl PumlSequenceParser {
}
Ok(ParticipantDef {
- is_create,
participant_type: participant_type.ok_or_else(|| {
SequenceError::InvalidStatement("missing participant type".to_string())
})?,
@@ -138,10 +163,11 @@ impl PumlSequenceParser {
fn parse_participant_identifier(
pair: pest::iterators::Pair,
) -> Result {
- let participant = pair
- .into_inner()
- .next()
- .expect("participant_identifier must contain a participant identifier");
+ let participant = pair.into_inner().next().ok_or_else(|| {
+ SequenceError::InvalidStatement(
+ "participant_identifier must contain a participant identifier".to_string(),
+ )
+ })?;
let participant_rule = participant.as_rule();
Ok(match participant_rule {
@@ -243,29 +269,14 @@ impl PumlSequenceParser {
pair: pest::iterators::Pair,
source_location: SourceLocation,
) -> Result {
- let mut left: Option = None;
- let mut arrow: Option = None;
- let mut right: Option = None;
+ let mut body: Option> = None;
let mut suffix: Option = None;
let mut description: Option = None;
for inner in pair.into_inner() {
match inner.as_rule() {
- Rule::message_endpoint => {
- let endpoint = Self::parse_message_endpoint(inner)?;
- // First participant goes to left, second to right
- if arrow.is_none() {
- left = Some(endpoint);
- } else {
- right = Some(endpoint);
- }
- }
- Rule::sequence_arrow => {
- arrow = Some(Self::parse_arrow(inner)?);
- }
- Rule::message_suffix => {
- suffix = Some(Self::parse_message_suffix(inner));
- }
+ Rule::message_body => body = Some(inner),
+ Rule::message_suffix => suffix = Some(Self::parse_message_suffix(inner)?),
Rule::sequence_description => {
description = inner
.into_inner()
@@ -276,71 +287,158 @@ impl PumlSequenceParser {
}
}
+ let (left, arrow, right) =
+ Self::parse_message_body(body.ok_or_else(|| {
+ SequenceError::InvalidStatement("missing message body".to_string())
+ })?)?;
+
Ok(Message {
- left: left.expect("message must contain left endpoint"),
- arrow: arrow.ok_or_else(|| {
- SequenceError::InvalidStatement("missing arrow in message".to_string())
- })?,
- right: right.expect("message must contain right endpoint"),
+ left,
+ arrow,
+ right,
suffix,
description,
source_location,
})
}
- fn parse_message_endpoint(
+ fn parse_message_body(
pair: pest::iterators::Pair,
- ) -> Result {
- let endpoint = pair
+ ) -> Result<(MessageEndpoint, Arrow, MessageEndpoint), SequenceError> {
+ let body = pair
.into_inner()
.next()
- .expect("message_endpoint must contain an endpoint");
+ .ok_or_else(|| SequenceError::InvalidStatement("empty message body".to_string()))?;
+ let body_rule = body.as_rule();
+ let mut endpoints = Vec::new();
+ let mut arrow = None;
+
+ for inner in body.into_inner() {
+ match inner.as_rule() {
+ Rule::message_endpoint => endpoints.push(Self::parse_message_endpoint(inner)?),
+ Rule::sequence_arrow => arrow = Some(Self::parse_arrow(inner)?),
+ _ => {}
+ }
+ }
+
+ let arrow = arrow.ok_or_else(|| {
+ SequenceError::InvalidStatement("message body must contain arrow".to_string())
+ })?;
+ let mut endpoints = endpoints.into_iter();
+
+ match body_rule {
+ Rule::message_full => Ok((
+ endpoints.next().ok_or_else(|| {
+ SequenceError::InvalidStatement(
+ "message_full must contain left endpoint".to_string(),
+ )
+ })?,
+ arrow,
+ endpoints.next().ok_or_else(|| {
+ SequenceError::InvalidStatement(
+ "message_full must contain right endpoint".to_string(),
+ )
+ })?,
+ )),
+ Rule::message_missing_left => Ok((
+ Self::missing_message_endpoint(),
+ arrow,
+ endpoints.next().ok_or_else(|| {
+ SequenceError::InvalidStatement(
+ "message_missing_left must contain right endpoint".to_string(),
+ )
+ })?,
+ )),
+ Rule::message_missing_right => Ok((
+ endpoints.next().ok_or_else(|| {
+ SequenceError::InvalidStatement(
+ "message_missing_right must contain left endpoint".to_string(),
+ )
+ })?,
+ arrow,
+ Self::missing_message_endpoint(),
+ )),
+ _ => Err(SequenceError::InvalidStatement(format!(
+ "unsupported message body: {:?}",
+ body_rule
+ ))),
+ }
+ }
+
+ fn missing_message_endpoint() -> MessageEndpoint {
+ MessageEndpoint::LostFound("?".to_string())
+ }
+
+ fn parse_message_endpoint(
+ pair: pest::iterators::Pair,
+ ) -> Result {
+ let endpoint = pair.into_inner().next().ok_or_else(|| {
+ SequenceError::InvalidStatement("message_endpoint must contain an endpoint".to_string())
+ })?;
Ok(match endpoint.as_rule() {
Rule::inline_participant => {
MessageEndpoint::Participant(Self::parse_participant_identifier(endpoint)?)
}
Rule::lost_found_marker => {
- MessageEndpoint::LostFound(Self::parse_lost_found_marker(endpoint))
+ MessageEndpoint::LostFound(Self::parse_lost_found_marker(endpoint)?)
+ }
+ _ => {
+ return Err(SequenceError::InvalidStatement(format!(
+ "message_endpoint grammar produced unsupported value: {:?}",
+ endpoint.as_rule()
+ )));
}
- _ => unreachable!(
- "message_endpoint grammar produced unsupported value: {:?}",
- endpoint.as_rule()
- ),
})
}
- fn parse_lost_found_marker(pair: pest::iterators::Pair) -> String {
- let marker = pair
- .into_inner()
- .next()
- .expect("lost_found_marker must contain a lost-found endpoint");
+ fn parse_lost_found_marker(pair: pest::iterators::Pair) -> Result {
+ let marker = pair.into_inner().next().ok_or_else(|| {
+ SequenceError::InvalidStatement(
+ "lost_found_marker must contain a lost-found endpoint".to_string(),
+ )
+ })?;
match marker.as_rule() {
- Rule::left_lost_found | Rule::right_lost_found => marker.as_str().trim().to_string(),
- _ => unreachable!(
+ Rule::left_lost_found | Rule::right_lost_found | Rule::short_lost_found => {
+ Ok(marker.as_str().trim().to_string())
+ }
+ _ => Err(SequenceError::InvalidStatement(format!(
"lost_found_marker grammar produced unsupported value: {:?}",
marker.as_rule()
- ),
+ ))),
}
}
- fn parse_message_suffix(pair: pest::iterators::Pair) -> MessageSuffix {
- let suffix = pair
+ fn parse_message_suffix(
+ pair: pest::iterators::Pair,
+ ) -> Result {
+ let suffixes: Vec<_> = pair
.into_inner()
- .next()
- .expect("message_suffix must contain a suffix");
+ .map(Self::parse_message_suffix_part)
+ .collect::>()?;
+
+ match suffixes.as_slice() {
+ [suffix] => Ok(suffix.clone()),
+ _ => Ok(MessageSuffix::Combined(suffixes)),
+ }
+ }
- match suffix.as_rule() {
+ fn parse_message_suffix_part(
+ pair: pest::iterators::Pair,
+ ) -> Result {
+ Ok(match pair.as_rule() {
Rule::activate_suffix => MessageSuffix::Activate,
Rule::deactivate_suffix => MessageSuffix::Deactivate,
Rule::create_suffix => MessageSuffix::Create,
Rule::destroy_suffix => MessageSuffix::Destroy,
- _ => unreachable!(
- "message_suffix grammar produced unsupported value: {:?}",
- suffix.as_rule()
- ),
- }
+ _ => {
+ return Err(SequenceError::InvalidStatement(format!(
+ "message_suffix grammar produced unsupported value: {:?}",
+ pair.as_rule()
+ )));
+ }
+ })
}
fn parse_arrow(pair: pest::iterators::Pair) -> Result {
@@ -352,85 +450,236 @@ impl PumlSequenceParser {
pair: pest::iterators::Pair,
source_location: SourceLocation,
) -> Result {
- let mut group_type: Option = None;
- let mut text: Option = None;
+ let inner = pair
+ .into_inner()
+ .next()
+ .ok_or_else(|| SequenceError::InvalidStatement("empty group command".to_string()))?;
+
+ match inner.as_rule() {
+ Rule::group_start => Self::parse_group_start(inner, source_location),
+ Rule::group_branch => Self::parse_group_branch(inner, source_location),
+ Rule::group_end => Self::parse_group_end(inner, source_location),
+ _ => Err(SequenceError::InvalidStatement(format!(
+ "unsupported group command: {:?}",
+ inner.as_rule()
+ ))),
+ }
+ }
+
+ fn parse_group_start(
+ pair: pest::iterators::Pair,
+ source_location: SourceLocation,
+ ) -> Result {
+ let mut kind: Option = None;
+ let mut label: Option = None;
+ let mut is_parallel = false;
for inner in pair.into_inner() {
match inner.as_rule() {
- Rule::group_type => {
- group_type = Self::parse_group_type(inner);
+ Rule::parallel_marker => {
+ is_parallel = true;
}
- Rule::group_condition => {
- text = Some(inner.as_str().trim().to_string());
+ Rule::group_start_type => {
+ kind = Some(Self::parse_group_kind(inner)?);
+ }
+ Rule::group_label => {
+ label = Some(inner.as_str().trim().to_string());
}
_ => {}
}
}
- Ok(GroupCmd {
- group_type: group_type
- .ok_or_else(|| SequenceError::InvalidStatement("missing group type".to_string()))?,
- text,
+ Ok(GroupCmd::Start(GroupStart {
+ kind: kind.ok_or_else(|| {
+ SequenceError::InvalidStatement("missing group start kind".to_string())
+ })?,
+ label,
+ is_parallel,
source_location,
- })
+ }))
}
- fn parse_group_type(pair: pest::iterators::Pair) -> Option {
+ fn parse_group_branch(
+ pair: pest::iterators::Pair,
+ source_location: SourceLocation,
+ ) -> Result {
+ let mut has_else = false;
+ let mut label: Option = None;
+
+ for inner in pair.into_inner() {
+ match inner.as_rule() {
+ Rule::group_branch_type => {
+ has_else = true;
+ }
+ Rule::group_label => {
+ label = Some(inner.as_str().trim().to_string());
+ }
+ _ => {}
+ }
+ }
+
+ if !has_else {
+ return Err(SequenceError::InvalidStatement(
+ "missing group branch kind".to_string(),
+ ));
+ }
+
+ Ok(GroupCmd::Else(GroupElse {
+ label,
+ source_location,
+ }))
+ }
+
+ fn parse_group_end(
+ pair: pest::iterators::Pair,
+ source_location: SourceLocation,
+ ) -> Result {
+ let mut kind: Option = None;
+
+ for inner in pair.into_inner() {
+ if inner.as_rule() == Rule::group_start_type {
+ kind = Some(Self::parse_group_kind(inner)?);
+ }
+ }
+
+ Ok(GroupCmd::End(GroupEnd {
+ kind,
+ source_location,
+ }))
+ }
+
+ fn parse_group_kind(pair: pest::iterators::Pair) -> Result {
let text = pair.as_str().to_lowercase();
- match text.as_str() {
- "opt" => Some(GroupType::Opt),
- "alt" => Some(GroupType::Alt),
- "loop" => Some(GroupType::Loop),
- "par" => Some(GroupType::Par),
- "par2" => Some(GroupType::Par2),
- "break" => Some(GroupType::Break),
- "critical" => Some(GroupType::Critical),
- "else" => Some(GroupType::Else),
- "also" => Some(GroupType::Also),
- "end" => Some(GroupType::End),
- "group" => Some(GroupType::Group),
- _ => None,
+ Ok(match text.as_str() {
+ "alt" => GroupKind::Alt,
+ "opt" => GroupKind::Opt,
+ "loop" => GroupKind::Loop,
+ "par" => GroupKind::Par,
+ "break" => GroupKind::Break,
+ "critical" => GroupKind::Critical,
+ "group" => GroupKind::Group,
+ _ => {
+ return Err(SequenceError::InvalidStatement(format!(
+ "unsupported group kind: {text}"
+ )));
+ }
+ })
+ }
+
+ fn parse_ref_cmd(pair: pest::iterators::Pair, source_location: SourceLocation) -> RefCmd {
+ let mut participants = Vec::new();
+ let mut text = None;
+
+ Self::extract_ref_parts(pair, &mut participants, &mut text);
+
+ RefCmd {
+ participants,
+ text,
+ source_location,
}
}
- fn parse_destroy_cmd(pair: pest::iterators::Pair) -> Result {
- let mut participant: Option = None;
+ fn parse_return_cmd(
+ pair: pest::iterators::Pair,
+ source_location: SourceLocation,
+ ) -> ReturnCmd {
+ let label = pair
+ .into_inner()
+ .find(|inner| inner.as_rule() == Rule::sequence_text_content)
+ .map(|inner| inner.as_str().trim().to_string())
+ .filter(|text| !text.is_empty());
+
+ ReturnCmd {
+ label,
+ source_location,
+ }
+ }
+
+ fn extract_ref_parts(
+ pair: pest::iterators::Pair,
+ participants: &mut Vec,
+ text: &mut Option,
+ ) {
+ match pair.as_rule() {
+ Rule::participant_list => {
+ participants.extend(pair.into_inner().filter_map(|inner| {
+ if inner.as_rule() == Rule::participant_ref {
+ Some(Self::parse_participant_ref(inner))
+ } else {
+ None
+ }
+ }));
+ }
+ Rule::sequence_text_content | Rule::ref_body => {
+ let value = pair.as_str().trim();
+ if !value.is_empty() {
+ *text = Some(value.to_string());
+ }
+ }
+ _ => {
+ for inner in pair.into_inner() {
+ Self::extract_ref_parts(inner, participants, text);
+ }
+ }
+ }
+ }
+
+ fn parse_create_cmd(
+ pair: pest::iterators::Pair,
+ source_location: SourceLocation,
+ ) -> Result {
+ let mut participant_type: Option = None;
+ let mut identifier: Option = None;
+ let mut stereotype: Option = None;
for inner in pair.into_inner() {
- if inner.as_rule() == Rule::participant_ref {
- participant = Some(Self::extract_participant_ref(inner));
+ match inner.as_rule() {
+ Rule::participant_type => {
+ participant_type = Some(Self::parse_participant_type(inner)?);
+ }
+ Rule::participant_identifier => {
+ identifier = Some(Self::parse_participant_identifier(inner)?);
+ }
+ Rule::stereotype => {
+ stereotype = Some(Self::extract_stereotype(inner.as_str()));
+ }
+ Rule::order_clause => {}
+ _ => {}
}
}
- Ok(DestroyCmd {
- participant: participant.ok_or_else(|| {
- SequenceError::InvalidStatement("missing participant in destroy".to_string())
+ Ok(CreateCmd {
+ participant_type: participant_type.unwrap_or(ParticipantType::Participant),
+ identifier: identifier.ok_or_else(|| {
+ SequenceError::InvalidStatement("missing participant identifier".to_string())
})?,
+ stereotype,
+ source_location,
})
}
- fn parse_create_cmd(pair: pest::iterators::Pair) -> Result {
- let mut participant: Option = None;
+ fn parse_destroy_cmd(pair: pest::iterators::Pair) -> Result {
+ let mut participant: Option = None;
for inner in pair.into_inner() {
if inner.as_rule() == Rule::participant_ref {
- participant = Some(Self::extract_participant_ref(inner));
+ participant = Some(Self::parse_participant_ref(inner));
}
}
- Ok(CreateCmd {
+ Ok(DestroyCmd {
participant: participant.ok_or_else(|| {
- SequenceError::InvalidStatement("missing participant in create".to_string())
+ SequenceError::InvalidStatement("missing participant in destroy".to_string())
})?,
})
}
fn parse_activate_cmd(pair: pest::iterators::Pair) -> Result {
- let mut participant: Option = None;
+ let mut participant: Option = None;
for inner in pair.into_inner() {
if inner.as_rule() == Rule::participant_ref {
- participant = Some(Self::extract_participant_ref(inner));
+ participant = Some(Self::parse_participant_ref(inner));
}
}
@@ -444,15 +693,53 @@ impl PumlSequenceParser {
fn parse_deactivate_cmd(
pair: pest::iterators::Pair,
) -> Result {
- let mut participant: Option = None;
+ let mut participant: Option = None;
for inner in pair.into_inner() {
if inner.as_rule() == Rule::participant_ref {
- participant = Some(Self::extract_participant_ref(inner));
+ participant = Some(Self::parse_participant_ref(inner));
}
}
- Ok(DeactivateCmd { participant })
+ Ok(DeactivateCmd {
+ participant: participant.ok_or_else(|| {
+ SequenceError::InvalidStatement("missing participant in deactivate".to_string())
+ })?,
+ })
+ }
+
+ fn parse_activation_short(
+ pair: pest::iterators::Pair,
+ ) -> Result, SequenceError> {
+ let mut parts = pair.into_inner();
+ let participant = parts
+ .next()
+ .filter(|part| part.as_rule() == Rule::participant_ref)
+ .map(Self::parse_participant_ref)
+ .ok_or_else(|| {
+ SequenceError::InvalidStatement(
+ "missing participant in short activation".to_string(),
+ )
+ })?;
+
+ match parts.next().map(|part| part.as_rule()).ok_or_else(|| {
+ SequenceError::InvalidStatement("missing short activation suffix".to_string())
+ })? {
+ Rule::activate_suffix => Ok(vec![Statement::ActivateCmd(ActivateCmd { participant })]),
+ Rule::deactivate_suffix => Ok(vec![Statement::DeactivateCmd(DeactivateCmd {
+ participant,
+ })]),
+ other => Err(SequenceError::InvalidStatement(format!(
+ "unsupported short activation suffix: {:?}",
+ other
+ ))),
+ }
+ }
+
+ fn parse_participant_ref(pair: pest::iterators::Pair) -> ParticipantRef {
+ ParticipantRef {
+ identifier: Self::extract_participant_ref(pair),
+ }
}
// Helper functions
@@ -470,47 +757,64 @@ impl PumlSequenceParser {
.to_string()
}
- /// Sequence participant names are stored as semantic identifiers, not as
- /// source literals. Quotation marks are PlantUML delimiters and are not
- /// part of the participant name in the parsed model.
- fn normalize_participant_name(s: &str) -> String {
- let value = s.trim();
- if value.starts_with('"') {
- Self::extract_quoted_string(value)
- } else {
- value.to_string()
- }
- }
-
fn extract_participant_ref(pair: pest::iterators::Pair) -> String {
match pair.as_rule() {
Rule::participant_ref => {
- let fallback = pair.as_str().trim();
-
+ let fallback = pair.as_str().trim().to_string();
pair.into_inner()
.next()
.map(Self::extract_participant_ref)
- .unwrap_or_else(|| Self::normalize_participant_name(fallback))
+ .unwrap_or(fallback)
}
+ Rule::CNAME => pair.as_str().trim().to_string(),
+ _ => pair.as_str().trim().to_string(),
+ }
+ }
- Rule::quoted_string => Self::extract_quoted_string(pair.as_str()),
-
- Rule::CNAME => Self::normalize_participant_name(pair.as_str()),
-
- Rule::quoted_display_with_alias | Rule::display_with_alias => pair
- .into_inner()
- .nth(1)
- .map(|p| p.as_str().trim().to_string())
- .unwrap_or_default(),
+ #[cfg(not(coverage))]
+ fn log_parse_tree_if_enabled(
+ pairs: &pest::iterators::Pairs,
+ path: &Rc,
+ log_level: LogLevel,
+ ) {
+ if matches!(log_level, LogLevel::Debug | LogLevel::Trace) {
+ let mut tree_output = String::new();
+ format_parse_tree(pairs.clone(), 0, &mut tree_output);
+ debug!(
+ "\n=== Parse Tree for {} ===\n{}=== End Parse Tree ===",
+ path.display(),
+ tree_output
+ );
+ }
+ }
- Rule::alias_with_quoted_display => pair
- .into_inner()
- .next()
- .map(|p| p.as_str().trim().to_string())
- .unwrap_or_default(),
+ fn parse_document(
+ pairs: pest::iterators::Pairs,
+ source_path: &str,
+ ) -> Result {
+ let mut document = SeqPumlDocument {
+ name: None,
+ statements: Vec::new(),
+ };
- _ => pair.as_str().trim().to_string(),
+ for inner_pair in pairs
+ .filter(|pair| pair.as_rule() == Rule::sequence_start)
+ .flat_map(|pair| pair.into_inner())
+ {
+ match inner_pair.as_rule() {
+ Rule::startuml => {
+ document.name = Self::parse_startuml(inner_pair);
+ }
+ Rule::sequence_statement => {
+ document
+ .statements
+ .extend(Self::parse_statement(inner_pair, source_path)?);
+ }
+ _ => {}
+ }
}
+
+ Ok(document)
}
}
@@ -534,45 +838,11 @@ impl DiagramParser for PumlSequenceParser {
let pairs = PlantUmlCommonParser::parse(Rule::sequence_start, content)
.map_err(|e| pest_to_syntax_error(e, path.as_ref().clone(), content))?;
- // Debug-only, excluded to keep coverage focused on parser logic.
#[cfg(not(coverage))]
- if matches!(log_level, LogLevel::Debug | LogLevel::Trace) {
- let mut tree_output = String::new();
- format_parse_tree(pairs.clone(), 0, &mut tree_output);
- debug!(
- "\n=== Parse Tree for {} ===\n{}=== End Parse Tree ===",
- path.display(),
- tree_output
- );
- }
+ Self::log_parse_tree_if_enabled(&pairs, path, log_level);
let source_path = path.as_ref().clone().to_string_lossy().to_string();
- let mut document = SeqPumlDocument {
- name: None,
- statements: Vec::new(),
- };
-
- for pair in pairs {
- if pair.as_rule() == Rule::sequence_start {
- for inner_pair in pair.into_inner() {
- match inner_pair.as_rule() {
- Rule::startuml => {
- document.name = Self::parse_startuml(inner_pair);
- }
- Rule::sequence_statement => {
- let mut stmts = Self::parse_statement(inner_pair, &source_path)?;
- document.statements.append(&mut stmts);
- }
- Rule::empty_line => {
- // Skip empty lines
- }
- _ => {}
- }
- }
- }
- }
-
- Ok(document)
+ Self::parse_document(pairs, &source_path)
}
}
@@ -677,4 +947,28 @@ mod dispatch_style_tests {
expected_file.as_str()
);
}
+
+ #[test]
+ fn test_statement_after_multiline_ref_is_preserved() {
+ let input = "@startuml\nref over Alice, Bob\n initialize service\nend ref\nAlice -> Bob : done\n@enduml";
+ let mut parser = PumlSequenceParser;
+ let doc = parser
+ .parse_file(&Rc::new(PathBuf::from("t.puml")), input, LogLevel::Info)
+ .expect("statement after multiline ref must parse");
+
+ match &doc.statements[0] {
+ Statement::RefCmd(ref_cmd) => {
+ assert_eq!(ref_cmd.text.as_deref(), Some("initialize service"));
+ }
+ actual => panic!("expected ref statement, got {:?}", actual),
+ }
+
+ match &doc.statements[1] {
+ Statement::Message(message) => {
+ assert_eq!(message.description.as_deref(), Some("done"));
+ assert_eq!(message.source_location.line, 5);
+ }
+ actual => panic!("expected message after ref statement, got {:?}", actual),
+ }
+ }
}
diff --git a/plantuml/parser/puml_parser/src/sequence_diagram/test/sequence_integration_test.rs b/plantuml/parser/puml_parser/src/sequence_diagram/test/sequence_integration_test.rs
index 556c0862..2223d4fc 100644
--- a/plantuml/parser/puml_parser/src/sequence_diagram/test/sequence_integration_test.rs
+++ b/plantuml/parser/puml_parser/src/sequence_diagram/test/sequence_integration_test.rs
@@ -71,7 +71,47 @@ fn test_message_participants() {
run_sequence_diagram_parser_case("message_participants");
}
+#[test]
+fn test_message_suffixes() {
+ run_sequence_diagram_parser_case("message_suffixes");
+}
+
#[test]
fn test_message_lost_found() {
run_sequence_diagram_parser_case("message_lost_found");
}
+
+#[test]
+fn test_message_missing_endpoints() {
+ run_sequence_diagram_parser_case("message_missing_endpoints");
+}
+
+#[test]
+fn test_create_participants() {
+ run_sequence_diagram_parser_case("create_participants");
+}
+
+#[test]
+fn test_lifecycle_commands() {
+ run_sequence_diagram_parser_case("lifecycle_commands");
+}
+
+#[test]
+fn test_group_commands() {
+ run_sequence_diagram_parser_case("group_commands");
+}
+
+#[test]
+fn test_ref_statement() {
+ run_sequence_diagram_parser_case("ref_statement");
+}
+
+#[test]
+fn test_return_commands() {
+ run_sequence_diagram_parser_case("return_commands");
+}
+
+#[test]
+fn test_ignored_blocks() {
+ run_sequence_diagram_parser_case("ignored_blocks");
+}
diff --git a/plantuml/parser/puml_parser/tests/sequence_diagram/create_participants/create_participants.puml b/plantuml/parser/puml_parser/tests/sequence_diagram/create_participants/create_participants.puml
new file mode 100644
index 00000000..001101fc
--- /dev/null
+++ b/plantuml/parser/puml_parser/tests/sequence_diagram/create_participants/create_participants.puml
@@ -0,0 +1,19 @@
+' *******************************************************************************
+' Copyright (c) 2026 Contributors to the Eclipse Foundation
+'
+' See the NOTICE file(s) distributed with this work for additional
+' information regarding copyright ownership.
+'
+' This program and the accompanying materials are made available under the
+' terms of the Apache License Version 2.0 which is available at
+' https://www.apache.org/licenses/LICENSE-2.0
+'
+' SPDX-License-Identifier: Apache-2.0
+' *******************************************************************************
+@startuml create_participants
+
+create participant "Created Worker" as Worker
+create Other
+create control String
+
+@enduml
diff --git a/plantuml/parser/puml_parser/tests/sequence_diagram/create_participants/output.json b/plantuml/parser/puml_parser/tests/sequence_diagram/create_participants/output.json
new file mode 100644
index 00000000..a650ea00
--- /dev/null
+++ b/plantuml/parser/puml_parser/tests/sequence_diagram/create_participants/output.json
@@ -0,0 +1,49 @@
+{
+ "create_participants.puml": {
+ "name": "create_participants",
+ "statements": [
+ {
+ "CreateCmd": {
+ "participant_type": "Participant",
+ "identifier": {
+ "display_name": "Created Worker",
+ "alias": "Worker"
+ },
+ "stereotype": null,
+ "source_location": {
+ "file": "",
+ "line": 15
+ }
+ }
+ },
+ {
+ "CreateCmd": {
+ "participant_type": "Participant",
+ "identifier": {
+ "display_name": "Other",
+ "alias": null
+ },
+ "stereotype": null,
+ "source_location": {
+ "file": "",
+ "line": 16
+ }
+ }
+ },
+ {
+ "CreateCmd": {
+ "participant_type": "Control",
+ "identifier": {
+ "display_name": "String",
+ "alias": null
+ },
+ "stereotype": null,
+ "source_location": {
+ "file": "",
+ "line": 17
+ }
+ }
+ }
+ ]
+ }
+}
diff --git a/plantuml/parser/puml_parser/tests/sequence_diagram/group_commands/group_commands.puml b/plantuml/parser/puml_parser/tests/sequence_diagram/group_commands/group_commands.puml
new file mode 100644
index 00000000..7f8794ca
--- /dev/null
+++ b/plantuml/parser/puml_parser/tests/sequence_diagram/group_commands/group_commands.puml
@@ -0,0 +1,41 @@
+' *******************************************************************************
+' Copyright (c) 2026 Contributors to the Eclipse Foundation
+'
+' See the NOTICE file(s) distributed with this work for additional
+' information regarding copyright ownership.
+'
+' This program and the accompanying materials are made available under the
+' terms of the Apache License Version 2.0 which is available at
+' https://www.apache.org/licenses/LICENSE-2.0
+'
+' SPDX-License-Identifier: Apache-2.0
+' *******************************************************************************
+@startuml group_commands
+
+alt successful path
+ Alice -> Bob : success
+else fallback path
+ Alice -> Bob : fallback
+end alt
+
+loop retry while pending
+ Alice -> Bob : retry
+end
+
+group custom section
+ Alice -> Bob : custom
+end
+
+par parallel branch
+ Alice -> Bob : branch
+end
+
+break stop early
+ Alice -> Bob : stop
+end
+
+critical exclusive section
+ Alice -> Bob : exclusive
+end critical
+
+@enduml
diff --git a/plantuml/parser/puml_parser/tests/sequence_diagram/group_commands/output.json b/plantuml/parser/puml_parser/tests/sequence_diagram/group_commands/output.json
new file mode 100644
index 00000000..f7b70ab3
--- /dev/null
+++ b/plantuml/parser/puml_parser/tests/sequence_diagram/group_commands/output.json
@@ -0,0 +1,386 @@
+{
+ "group_commands.puml": {
+ "name": "group_commands",
+ "statements": [
+ {
+ "GroupCmd": {
+ "Start": {
+ "kind": "Alt",
+ "label": "successful path",
+ "is_parallel": false,
+ "source_location": {
+ "file": "",
+ "line": 15
+ }
+ }
+ }
+ },
+ {
+ "Message": {
+ "left": {
+ "Participant": {
+ "display_name": "Alice",
+ "alias": null
+ }
+ },
+ "arrow": {
+ "left": null,
+ "line": {
+ "raw": "-"
+ },
+ "middle": null,
+ "right": {
+ "raw": ">"
+ }
+ },
+ "right": {
+ "Participant": {
+ "display_name": "Bob",
+ "alias": null
+ }
+ },
+ "suffix": null,
+ "description": "success",
+ "source_location": {
+ "file": "",
+ "line": 16
+ }
+ }
+ },
+ {
+ "GroupCmd": {
+ "Else": {
+ "label": "fallback path",
+ "source_location": {
+ "file": "",
+ "line": 17
+ }
+ }
+ }
+ },
+ {
+ "Message": {
+ "left": {
+ "Participant": {
+ "display_name": "Alice",
+ "alias": null
+ }
+ },
+ "arrow": {
+ "left": null,
+ "line": {
+ "raw": "-"
+ },
+ "middle": null,
+ "right": {
+ "raw": ">"
+ }
+ },
+ "right": {
+ "Participant": {
+ "display_name": "Bob",
+ "alias": null
+ }
+ },
+ "suffix": null,
+ "description": "fallback",
+ "source_location": {
+ "file": "",
+ "line": 18
+ }
+ }
+ },
+ {
+ "GroupCmd": {
+ "End": {
+ "kind": "Alt",
+ "source_location": {
+ "file": "",
+ "line": 19
+ }
+ }
+ }
+ },
+ {
+ "GroupCmd": {
+ "Start": {
+ "kind": "Loop",
+ "label": "retry while pending",
+ "is_parallel": false,
+ "source_location": {
+ "file": "",
+ "line": 21
+ }
+ }
+ }
+ },
+ {
+ "Message": {
+ "left": {
+ "Participant": {
+ "display_name": "Alice",
+ "alias": null
+ }
+ },
+ "arrow": {
+ "left": null,
+ "line": {
+ "raw": "-"
+ },
+ "middle": null,
+ "right": {
+ "raw": ">"
+ }
+ },
+ "right": {
+ "Participant": {
+ "display_name": "Bob",
+ "alias": null
+ }
+ },
+ "suffix": null,
+ "description": "retry",
+ "source_location": {
+ "file": "",
+ "line": 22
+ }
+ }
+ },
+ {
+ "GroupCmd": {
+ "End": {
+ "kind": null,
+ "source_location": {
+ "file": "",
+ "line": 23
+ }
+ }
+ }
+ },
+ {
+ "GroupCmd": {
+ "Start": {
+ "kind": "Group",
+ "label": "custom section",
+ "is_parallel": false,
+ "source_location": {
+ "file": "",
+ "line": 25
+ }
+ }
+ }
+ },
+ {
+ "Message": {
+ "left": {
+ "Participant": {
+ "display_name": "Alice",
+ "alias": null
+ }
+ },
+ "arrow": {
+ "left": null,
+ "line": {
+ "raw": "-"
+ },
+ "middle": null,
+ "right": {
+ "raw": ">"
+ }
+ },
+ "right": {
+ "Participant": {
+ "display_name": "Bob",
+ "alias": null
+ }
+ },
+ "suffix": null,
+ "description": "custom",
+ "source_location": {
+ "file": "",
+ "line": 26
+ }
+ }
+ },
+ {
+ "GroupCmd": {
+ "End": {
+ "kind": null,
+ "source_location": {
+ "file": "",
+ "line": 27
+ }
+ }
+ }
+ },
+ {
+ "GroupCmd": {
+ "Start": {
+ "kind": "Par",
+ "label": "parallel branch",
+ "is_parallel": false,
+ "source_location": {
+ "file": "",
+ "line": 29
+ }
+ }
+ }
+ },
+ {
+ "Message": {
+ "left": {
+ "Participant": {
+ "display_name": "Alice",
+ "alias": null
+ }
+ },
+ "arrow": {
+ "left": null,
+ "line": {
+ "raw": "-"
+ },
+ "middle": null,
+ "right": {
+ "raw": ">"
+ }
+ },
+ "right": {
+ "Participant": {
+ "display_name": "Bob",
+ "alias": null
+ }
+ },
+ "suffix": null,
+ "description": "branch",
+ "source_location": {
+ "file": "",
+ "line": 30
+ }
+ }
+ },
+ {
+ "GroupCmd": {
+ "End": {
+ "kind": null,
+ "source_location": {
+ "file": "",
+ "line": 31
+ }
+ }
+ }
+ },
+ {
+ "GroupCmd": {
+ "Start": {
+ "kind": "Break",
+ "label": "stop early",
+ "is_parallel": false,
+ "source_location": {
+ "file": "",
+ "line": 33
+ }
+ }
+ }
+ },
+ {
+ "Message": {
+ "left": {
+ "Participant": {
+ "display_name": "Alice",
+ "alias": null
+ }
+ },
+ "arrow": {
+ "left": null,
+ "line": {
+ "raw": "-"
+ },
+ "middle": null,
+ "right": {
+ "raw": ">"
+ }
+ },
+ "right": {
+ "Participant": {
+ "display_name": "Bob",
+ "alias": null
+ }
+ },
+ "suffix": null,
+ "description": "stop",
+ "source_location": {
+ "file": "",
+ "line": 34
+ }
+ }
+ },
+ {
+ "GroupCmd": {
+ "End": {
+ "kind": null,
+ "source_location": {
+ "file": "",
+ "line": 35
+ }
+ }
+ }
+ },
+ {
+ "GroupCmd": {
+ "Start": {
+ "kind": "Critical",
+ "label": "exclusive section",
+ "is_parallel": false,
+ "source_location": {
+ "file": "",
+ "line": 37
+ }
+ }
+ }
+ },
+ {
+ "Message": {
+ "left": {
+ "Participant": {
+ "display_name": "Alice",
+ "alias": null
+ }
+ },
+ "arrow": {
+ "left": null,
+ "line": {
+ "raw": "-"
+ },
+ "middle": null,
+ "right": {
+ "raw": ">"
+ }
+ },
+ "right": {
+ "Participant": {
+ "display_name": "Bob",
+ "alias": null
+ }
+ },
+ "suffix": null,
+ "description": "exclusive",
+ "source_location": {
+ "file": "",
+ "line": 38
+ }
+ }
+ },
+ {
+ "GroupCmd": {
+ "End": {
+ "kind": "Critical",
+ "source_location": {
+ "file": "",
+ "line": 39
+ }
+ }
+ }
+ }
+ ]
+ }
+}
diff --git a/plantuml/parser/puml_parser/tests/sequence_diagram/ignored_blocks/ignored_blocks.puml b/plantuml/parser/puml_parser/tests/sequence_diagram/ignored_blocks/ignored_blocks.puml
new file mode 100644
index 00000000..84170613
--- /dev/null
+++ b/plantuml/parser/puml_parser/tests/sequence_diagram/ignored_blocks/ignored_blocks.puml
@@ -0,0 +1,35 @@
+' *******************************************************************************
+' Copyright (c) 2026 Contributors to the Eclipse Foundation
+'
+' See the NOTICE file(s) distributed with this work for additional
+' information regarding copyright ownership.
+'
+' This program and the accompanying materials are made available under the
+' terms of the Apache License Version 2.0 which is available at
+' https://www.apache.org/licenses/LICENSE-2.0
+'
+' SPDX-License-Identifier: Apache-2.0
+' *******************************************************************************
+@startuml ignored_blocks
+
+
+
+legend right
+ rendered by PlantUML only
+end legend
+
+!transformation {
+ noop
+!}
+
+sprite $icon {
+ 0000
+}
+
+Alice -> Bob : after ignored blocks
+
+@enduml
diff --git a/plantuml/parser/puml_parser/tests/sequence_diagram/ignored_blocks/output.json b/plantuml/parser/puml_parser/tests/sequence_diagram/ignored_blocks/output.json
new file mode 100644
index 00000000..c3b18064
--- /dev/null
+++ b/plantuml/parser/puml_parser/tests/sequence_diagram/ignored_blocks/output.json
@@ -0,0 +1,39 @@
+{
+ "ignored_blocks.puml": {
+ "name": "ignored_blocks",
+ "statements": [
+ {
+ "Message": {
+ "left": {
+ "Participant": {
+ "display_name": "Alice",
+ "alias": null
+ }
+ },
+ "arrow": {
+ "left": null,
+ "line": {
+ "raw": "-"
+ },
+ "middle": null,
+ "right": {
+ "raw": ">"
+ }
+ },
+ "right": {
+ "Participant": {
+ "display_name": "Bob",
+ "alias": null
+ }
+ },
+ "suffix": null,
+ "description": "after ignored blocks",
+ "source_location": {
+ "file": "",
+ "line": 33
+ }
+ }
+ }
+ ]
+ }
+}
diff --git a/plantuml/parser/puml_parser/tests/sequence_diagram/lifecycle_commands/lifecycle_commands.puml b/plantuml/parser/puml_parser/tests/sequence_diagram/lifecycle_commands/lifecycle_commands.puml
new file mode 100644
index 00000000..ccd1f20d
--- /dev/null
+++ b/plantuml/parser/puml_parser/tests/sequence_diagram/lifecycle_commands/lifecycle_commands.puml
@@ -0,0 +1,22 @@
+' *******************************************************************************
+' Copyright (c) 2026 Contributors to the Eclipse Foundation
+'
+' See the NOTICE file(s) distributed with this work for additional
+' information regarding copyright ownership.
+'
+' This program and the accompanying materials are made available under the
+' terms of the Apache License Version 2.0 which is available at
+' https://www.apache.org/licenses/LICENSE-2.0
+'
+' SPDX-License-Identifier: Apache-2.0
+' *******************************************************************************
+@startuml lifecycle_commands
+
+activate alice
+activate bob #Gold
+deactivate alice
+destroy bob
+peter++
+peter --
+
+@enduml
diff --git a/plantuml/parser/puml_parser/tests/sequence_diagram/lifecycle_commands/output.json b/plantuml/parser/puml_parser/tests/sequence_diagram/lifecycle_commands/output.json
new file mode 100644
index 00000000..55c6431f
--- /dev/null
+++ b/plantuml/parser/puml_parser/tests/sequence_diagram/lifecycle_commands/output.json
@@ -0,0 +1,49 @@
+{
+ "lifecycle_commands.puml": {
+ "name": "lifecycle_commands",
+ "statements": [
+ {
+ "ActivateCmd": {
+ "participant": {
+ "identifier": "alice"
+ }
+ }
+ },
+ {
+ "ActivateCmd": {
+ "participant": {
+ "identifier": "bob"
+ }
+ }
+ },
+ {
+ "DeactivateCmd": {
+ "participant": {
+ "identifier": "alice"
+ }
+ }
+ },
+ {
+ "DestroyCmd": {
+ "participant": {
+ "identifier": "bob"
+ }
+ }
+ },
+ {
+ "ActivateCmd": {
+ "participant": {
+ "identifier": "peter"
+ }
+ }
+ },
+ {
+ "DeactivateCmd": {
+ "participant": {
+ "identifier": "peter"
+ }
+ }
+ }
+ ]
+ }
+}
diff --git a/plantuml/parser/puml_parser/tests/sequence_diagram/message_lost_found/message_lost_found.puml b/plantuml/parser/puml_parser/tests/sequence_diagram/message_lost_found/message_lost_found.puml
index f3f46416..5f2b2189 100644
--- a/plantuml/parser/puml_parser/tests/sequence_diagram/message_lost_found/message_lost_found.puml
+++ b/plantuml/parser/puml_parser/tests/sequence_diagram/message_lost_found/message_lost_found.puml
@@ -12,11 +12,19 @@
' *******************************************************************************
@startuml message_lost_found
-[ -> Service : left lost bracket
-[o -> Service ++ : left lost bracket o
-[x -> Service ** : left lost bracket x
-Worker -> ] : right lost bracket
-Worker -> o] -- : right lost o bracket
-Worker -> x] !! : right lost x bracket
+[-> Service
+[o-> Service
+[o->o Service
+[x-> Service
+[<- Service
+[x<- Service
+Service ->]
+Service ->o]
+Service o->o]
+Service ->x]
+Service <-]
+Service x<-]
+?-> Worker : short to Worker
+Worker ->? : short from Worker
@enduml
diff --git a/plantuml/parser/puml_parser/tests/sequence_diagram/message_lost_found/output.json b/plantuml/parser/puml_parser/tests/sequence_diagram/message_lost_found/output.json
index fc34b94d..f8a210f6 100644
--- a/plantuml/parser/puml_parser/tests/sequence_diagram/message_lost_found/output.json
+++ b/plantuml/parser/puml_parser/tests/sequence_diagram/message_lost_found/output.json
@@ -24,7 +24,7 @@
}
},
"suffix": null,
- "description": "left lost bracket",
+ "description": null,
"source_location": {
"file": "",
"line": 15
@@ -34,10 +34,12 @@
{
"Message": {
"left": {
- "LostFound": "[o"
+ "LostFound": "["
},
"arrow": {
- "left": null,
+ "left": {
+ "raw": "o"
+ },
"line": {
"raw": "-"
},
@@ -52,8 +54,8 @@
"alias": null
}
},
- "suffix": "Activate",
- "description": "left lost bracket o",
+ "suffix": null,
+ "description": null,
"source_location": {
"file": "",
"line": 16
@@ -63,16 +65,18 @@
{
"Message": {
"left": {
- "LostFound": "[x"
+ "LostFound": "["
},
"arrow": {
- "left": null,
+ "left": {
+ "raw": "o"
+ },
"line": {
"raw": "-"
},
"middle": null,
"right": {
- "raw": ">"
+ "raw": ">o"
}
},
"right": {
@@ -81,8 +85,8 @@
"alias": null
}
},
- "suffix": "Create",
- "description": "left lost bracket x",
+ "suffix": null,
+ "description": null,
"source_location": {
"file": "",
"line": 17
@@ -92,8 +96,97 @@
{
"Message": {
"left": {
+ "LostFound": "["
+ },
+ "arrow": {
+ "left": {
+ "raw": "x"
+ },
+ "line": {
+ "raw": "-"
+ },
+ "middle": null,
+ "right": {
+ "raw": ">"
+ }
+ },
+ "right": {
"Participant": {
- "display_name": "Worker",
+ "display_name": "Service",
+ "alias": null
+ }
+ },
+ "suffix": null,
+ "description": null,
+ "source_location": {
+ "file": "",
+ "line": 18
+ }
+ }
+ },
+ {
+ "Message": {
+ "left": {
+ "LostFound": "["
+ },
+ "arrow": {
+ "left": {
+ "raw": "<"
+ },
+ "line": {
+ "raw": "-"
+ },
+ "middle": null,
+ "right": null
+ },
+ "right": {
+ "Participant": {
+ "display_name": "Service",
+ "alias": null
+ }
+ },
+ "suffix": null,
+ "description": null,
+ "source_location": {
+ "file": "",
+ "line": 19
+ }
+ }
+ },
+ {
+ "Message": {
+ "left": {
+ "LostFound": "["
+ },
+ "arrow": {
+ "left": {
+ "raw": "x<"
+ },
+ "line": {
+ "raw": "-"
+ },
+ "middle": null,
+ "right": null
+ },
+ "right": {
+ "Participant": {
+ "display_name": "Service",
+ "alias": null
+ }
+ },
+ "suffix": null,
+ "description": null,
+ "source_location": {
+ "file": "",
+ "line": 20
+ }
+ }
+ },
+ {
+ "Message": {
+ "left": {
+ "Participant": {
+ "display_name": "Service",
"alias": null
}
},
@@ -111,10 +204,10 @@
"LostFound": "]"
},
"suffix": null,
- "description": "right lost bracket",
+ "description": null,
"source_location": {
"file": "",
- "line": 18
+ "line": 21
}
}
},
@@ -122,10 +215,154 @@
"Message": {
"left": {
"Participant": {
- "display_name": "Worker",
+ "display_name": "Service",
+ "alias": null
+ }
+ },
+ "arrow": {
+ "left": null,
+ "line": {
+ "raw": "-"
+ },
+ "middle": null,
+ "right": {
+ "raw": ">o"
+ }
+ },
+ "right": {
+ "LostFound": "]"
+ },
+ "suffix": null,
+ "description": null,
+ "source_location": {
+ "file": "",
+ "line": 22
+ }
+ }
+ },
+ {
+ "Message": {
+ "left": {
+ "Participant": {
+ "display_name": "Service",
+ "alias": null
+ }
+ },
+ "arrow": {
+ "left": {
+ "raw": "o"
+ },
+ "line": {
+ "raw": "-"
+ },
+ "middle": null,
+ "right": {
+ "raw": ">o"
+ }
+ },
+ "right": {
+ "LostFound": "]"
+ },
+ "suffix": null,
+ "description": null,
+ "source_location": {
+ "file": "",
+ "line": 23
+ }
+ }
+ },
+ {
+ "Message": {
+ "left": {
+ "Participant": {
+ "display_name": "Service",
"alias": null
}
},
+ "arrow": {
+ "left": null,
+ "line": {
+ "raw": "-"
+ },
+ "middle": null,
+ "right": {
+ "raw": ">x"
+ }
+ },
+ "right": {
+ "LostFound": "]"
+ },
+ "suffix": null,
+ "description": null,
+ "source_location": {
+ "file": "",
+ "line": 24
+ }
+ }
+ },
+ {
+ "Message": {
+ "left": {
+ "Participant": {
+ "display_name": "Service",
+ "alias": null
+ }
+ },
+ "arrow": {
+ "left": {
+ "raw": "<"
+ },
+ "line": {
+ "raw": "-"
+ },
+ "middle": null,
+ "right": null
+ },
+ "right": {
+ "LostFound": "]"
+ },
+ "suffix": null,
+ "description": null,
+ "source_location": {
+ "file": "",
+ "line": 25
+ }
+ }
+ },
+ {
+ "Message": {
+ "left": {
+ "Participant": {
+ "display_name": "Service",
+ "alias": null
+ }
+ },
+ "arrow": {
+ "left": {
+ "raw": "x<"
+ },
+ "line": {
+ "raw": "-"
+ },
+ "middle": null,
+ "right": null
+ },
+ "right": {
+ "LostFound": "]"
+ },
+ "suffix": null,
+ "description": null,
+ "source_location": {
+ "file": "",
+ "line": 26
+ }
+ }
+ },
+ {
+ "Message": {
+ "left": {
+ "LostFound": "?"
+ },
"arrow": {
"left": null,
"line": {
@@ -137,13 +374,16 @@
}
},
"right": {
- "LostFound": "o]"
+ "Participant": {
+ "display_name": "Worker",
+ "alias": null
+ }
},
- "suffix": "Deactivate",
- "description": "right lost o bracket",
+ "suffix": null,
+ "description": "short to Worker",
"source_location": {
"file": "",
- "line": 19
+ "line": 27
}
}
},
@@ -166,13 +406,13 @@
}
},
"right": {
- "LostFound": "x]"
+ "LostFound": "?"
},
- "suffix": "Destroy",
- "description": "right lost x bracket",
+ "suffix": null,
+ "description": "short from Worker",
"source_location": {
"file": "",
- "line": 20
+ "line": 28
}
}
}
diff --git a/plantuml/parser/puml_parser/tests/sequence_diagram/message_missing_endpoints/message_missing_endpoints.puml b/plantuml/parser/puml_parser/tests/sequence_diagram/message_missing_endpoints/message_missing_endpoints.puml
new file mode 100644
index 00000000..80a03e2d
--- /dev/null
+++ b/plantuml/parser/puml_parser/tests/sequence_diagram/message_missing_endpoints/message_missing_endpoints.puml
@@ -0,0 +1,20 @@
+' *******************************************************************************
+' Copyright (c) 2026 Contributors to the Eclipse Foundation
+'
+' See the NOTICE file(s) distributed with this work for additional
+' information regarding copyright ownership.
+'
+' This program and the accompanying materials are made available under the
+' terms of the Apache License Version 2.0 which is available at
+' https://www.apache.org/licenses/LICENSE-2.0
+'
+' SPDX-License-Identifier: Apache-2.0
+' *******************************************************************************
+@startuml message_missing_endpoints
+
+--> Service : missing left
+Worker <-- : missing right
+--> Service
+Worker <--
+
+@enduml
diff --git a/plantuml/parser/puml_parser/tests/sequence_diagram/message_missing_endpoints/output.json b/plantuml/parser/puml_parser/tests/sequence_diagram/message_missing_endpoints/output.json
new file mode 100644
index 00000000..c97e8679
--- /dev/null
+++ b/plantuml/parser/puml_parser/tests/sequence_diagram/message_missing_endpoints/output.json
@@ -0,0 +1,123 @@
+{
+ "message_missing_endpoints.puml": {
+ "name": "message_missing_endpoints",
+ "statements": [
+ {
+ "Message": {
+ "left": {
+ "LostFound": "?"
+ },
+ "arrow": {
+ "left": null,
+ "line": {
+ "raw": "--"
+ },
+ "middle": null,
+ "right": {
+ "raw": ">"
+ }
+ },
+ "right": {
+ "Participant": {
+ "display_name": "Service",
+ "alias": null
+ }
+ },
+ "suffix": null,
+ "description": "missing left",
+ "source_location": {
+ "file": "",
+ "line": 15
+ }
+ }
+ },
+ {
+ "Message": {
+ "left": {
+ "Participant": {
+ "display_name": "Worker",
+ "alias": null
+ }
+ },
+ "arrow": {
+ "left": {
+ "raw": "<"
+ },
+ "line": {
+ "raw": "--"
+ },
+ "middle": null,
+ "right": null
+ },
+ "right": {
+ "LostFound": "?"
+ },
+ "suffix": null,
+ "description": "missing right",
+ "source_location": {
+ "file": "",
+ "line": 16
+ }
+ }
+ },
+ {
+ "Message": {
+ "left": {
+ "LostFound": "?"
+ },
+ "arrow": {
+ "left": null,
+ "line": {
+ "raw": "--"
+ },
+ "middle": null,
+ "right": {
+ "raw": ">"
+ }
+ },
+ "right": {
+ "Participant": {
+ "display_name": "Service",
+ "alias": null
+ }
+ },
+ "suffix": null,
+ "description": null,
+ "source_location": {
+ "file": "",
+ "line": 17
+ }
+ }
+ },
+ {
+ "Message": {
+ "left": {
+ "Participant": {
+ "display_name": "Worker",
+ "alias": null
+ }
+ },
+ "arrow": {
+ "left": {
+ "raw": "<"
+ },
+ "line": {
+ "raw": "--"
+ },
+ "middle": null,
+ "right": null
+ },
+ "right": {
+ "LostFound": "?"
+ },
+ "suffix": null,
+ "description": null,
+ "source_location": {
+ "file": "",
+ "line": 18
+ }
+ }
+ }
+ ]
+ }
+}
diff --git a/plantuml/parser/puml_parser/tests/sequence_diagram/message_suffixes/message_suffixes.puml b/plantuml/parser/puml_parser/tests/sequence_diagram/message_suffixes/message_suffixes.puml
new file mode 100644
index 00000000..10c2bfbb
--- /dev/null
+++ b/plantuml/parser/puml_parser/tests/sequence_diagram/message_suffixes/message_suffixes.puml
@@ -0,0 +1,19 @@
+' *******************************************************************************
+' Copyright (c) 2026 Contributors to the Eclipse Foundation
+'
+' See the NOTICE file(s) distributed with this work for additional
+' information regarding copyright ownership.
+'
+' This program and the accompanying materials are made available under the
+' terms of the Apache License Version 2.0 which is available at
+' https://www.apache.org/licenses/LICENSE-2.0
+'
+' SPDX-License-Identifier: Apache-2.0
+' *******************************************************************************
+@startuml message_suffixes
+
+alice -> bob ++ #LightBlue : hello1
+bob -> charlie --++ : hello2
+charlie --> alice -- : ok
+
+@enduml
diff --git a/plantuml/parser/puml_parser/tests/sequence_diagram/message_suffixes/output.json b/plantuml/parser/puml_parser/tests/sequence_diagram/message_suffixes/output.json
new file mode 100644
index 00000000..33aaa535
--- /dev/null
+++ b/plantuml/parser/puml_parser/tests/sequence_diagram/message_suffixes/output.json
@@ -0,0 +1,108 @@
+{
+ "message_suffixes.puml": {
+ "name": "message_suffixes",
+ "statements": [
+ {
+ "Message": {
+ "left": {
+ "Participant": {
+ "display_name": "alice",
+ "alias": null
+ }
+ },
+ "arrow": {
+ "left": null,
+ "line": {
+ "raw": "-"
+ },
+ "middle": null,
+ "right": {
+ "raw": ">"
+ }
+ },
+ "right": {
+ "Participant": {
+ "display_name": "bob",
+ "alias": null
+ }
+ },
+ "suffix": "Activate",
+ "description": "hello1",
+ "source_location": {
+ "file": "",
+ "line": 15
+ }
+ }
+ },
+ {
+ "Message": {
+ "left": {
+ "Participant": {
+ "display_name": "bob",
+ "alias": null
+ }
+ },
+ "arrow": {
+ "left": null,
+ "line": {
+ "raw": "-"
+ },
+ "middle": null,
+ "right": {
+ "raw": ">"
+ }
+ },
+ "right": {
+ "Participant": {
+ "display_name": "charlie",
+ "alias": null
+ }
+ },
+ "suffix": {
+ "Combined": [
+ "Deactivate",
+ "Activate"
+ ]
+ },
+ "description": "hello2",
+ "source_location": {
+ "file": "",
+ "line": 16
+ }
+ }
+ },
+ {
+ "Message": {
+ "left": {
+ "Participant": {
+ "display_name": "charlie",
+ "alias": null
+ }
+ },
+ "arrow": {
+ "left": null,
+ "line": {
+ "raw": "--"
+ },
+ "middle": null,
+ "right": {
+ "raw": ">"
+ }
+ },
+ "right": {
+ "Participant": {
+ "display_name": "alice",
+ "alias": null
+ }
+ },
+ "suffix": "Deactivate",
+ "description": "ok",
+ "source_location": {
+ "file": "",
+ "line": 17
+ }
+ }
+ }
+ ]
+ }
+}
diff --git a/plantuml/parser/puml_parser/tests/sequence_diagram/participant_identifiers/output.json b/plantuml/parser/puml_parser/tests/sequence_diagram/participant_identifiers/output.json
index beaab559..6ace0f3d 100644
--- a/plantuml/parser/puml_parser/tests/sequence_diagram/participant_identifiers/output.json
+++ b/plantuml/parser/puml_parser/tests/sequence_diagram/participant_identifiers/output.json
@@ -72,21 +72,6 @@
}
}
},
- {
- "ParticipantDef": {
- "is_create": true,
- "participant_type": "Participant",
- "identifier": {
- "display_name": "Created Worker",
- "alias": "Worker"
- },
- "stereotype": null,
- "source_location": {
- "file": "",
- "line": 23
- }
- }
- },
{
"ParticipantDef": {
"participant_type": "Participant",
@@ -97,7 +82,7 @@
"stereotype": "component",
"source_location": {
"file": "",
- "line": 26
+ "line": 23
}
}
}
diff --git a/plantuml/parser/puml_parser/tests/sequence_diagram/participant_identifiers/participant_identifiers.puml b/plantuml/parser/puml_parser/tests/sequence_diagram/participant_identifiers/participant_identifiers.puml
index aa5397d2..18e9c986 100644
--- a/plantuml/parser/puml_parser/tests/sequence_diagram/participant_identifiers/participant_identifiers.puml
+++ b/plantuml/parser/puml_parser/tests/sequence_diagram/participant_identifiers/participant_identifiers.puml
@@ -19,9 +19,6 @@ boundary Gateway as GatewayAlias
control Controller
queue "Work Queue"
-' create participant declaration
-create participant "Created Worker" as Worker
-
' participant declaration with stereotype and order
participant LastOrder <> order 1
diff --git a/plantuml/parser/puml_parser/tests/sequence_diagram/ref_statement/output.json b/plantuml/parser/puml_parser/tests/sequence_diagram/ref_statement/output.json
new file mode 100644
index 00000000..49b251a7
--- /dev/null
+++ b/plantuml/parser/puml_parser/tests/sequence_diagram/ref_statement/output.json
@@ -0,0 +1,41 @@
+{
+ "ref_statement.puml": {
+ "name": "ref_statement",
+ "statements": [
+ {
+ "RefCmd": {
+ "participants": [
+ {
+ "identifier": "Alice"
+ },
+ {
+ "identifier": "Bob"
+ }
+ ],
+ "text": "initialize service",
+ "source_location": {
+ "file": "",
+ "line": 15
+ }
+ }
+ },
+ {
+ "RefCmd": {
+ "participants": [
+ {
+ "identifier": "Alice"
+ },
+ {
+ "identifier": "Bob"
+ }
+ ],
+ "text": "perform remote operation\n validate response",
+ "source_location": {
+ "file": "",
+ "line": 16
+ }
+ }
+ }
+ ]
+ }
+}
diff --git a/plantuml/parser/puml_parser/tests/sequence_diagram/ref_statement/ref_statement.puml b/plantuml/parser/puml_parser/tests/sequence_diagram/ref_statement/ref_statement.puml
new file mode 100644
index 00000000..3164b10d
--- /dev/null
+++ b/plantuml/parser/puml_parser/tests/sequence_diagram/ref_statement/ref_statement.puml
@@ -0,0 +1,21 @@
+' *******************************************************************************
+' Copyright (c) 2026 Contributors to the Eclipse Foundation
+'
+' See the NOTICE file(s) distributed with this work for additional
+' information regarding copyright ownership.
+'
+' This program and the accompanying materials are made available under the
+' terms of the Apache License Version 2.0 which is available at
+' https://www.apache.org/licenses/LICENSE-2.0
+'
+' SPDX-License-Identifier: Apache-2.0
+' *******************************************************************************
+@startuml ref_statement
+
+ref over Alice, Bob : initialize service
+ref over Alice, Bob
+ perform remote operation
+ validate response
+end ref
+
+@enduml
diff --git a/plantuml/parser/puml_parser/tests/sequence_diagram/return_commands/output.json b/plantuml/parser/puml_parser/tests/sequence_diagram/return_commands/output.json
new file mode 100644
index 00000000..8e2f3aa8
--- /dev/null
+++ b/plantuml/parser/puml_parser/tests/sequence_diagram/return_commands/output.json
@@ -0,0 +1,89 @@
+{
+ "return_commands.puml": {
+ "name": "return_commands",
+ "statements": [
+ {
+ "Message": {
+ "left": {
+ "Participant": {
+ "display_name": "Alice",
+ "alias": null
+ }
+ },
+ "arrow": {
+ "left": null,
+ "line": {
+ "raw": "-"
+ },
+ "middle": null,
+ "right": {
+ "raw": ">"
+ }
+ },
+ "right": {
+ "Participant": {
+ "display_name": "Bob",
+ "alias": null
+ }
+ },
+ "suffix": null,
+ "description": "call",
+ "source_location": {
+ "file": "",
+ "line": 15
+ }
+ }
+ },
+ {
+ "ReturnCmd": {
+ "label": "done",
+ "source_location": {
+ "file": "",
+ "line": 16
+ }
+ }
+ },
+ {
+ "Message": {
+ "left": {
+ "Participant": {
+ "display_name": "Alice",
+ "alias": null
+ }
+ },
+ "arrow": {
+ "left": null,
+ "line": {
+ "raw": "-"
+ },
+ "middle": null,
+ "right": {
+ "raw": ">"
+ }
+ },
+ "right": {
+ "Participant": {
+ "display_name": "Bob",
+ "alias": null
+ }
+ },
+ "suffix": null,
+ "description": "ping",
+ "source_location": {
+ "file": "",
+ "line": 17
+ }
+ }
+ },
+ {
+ "ReturnCmd": {
+ "label": "Result>>",
+ "source_location": {
+ "file": "",
+ "line": 18
+ }
+ }
+ }
+ ]
+ }
+}
diff --git a/plantuml/parser/puml_parser/tests/sequence_diagram/return_commands/return_commands.puml b/plantuml/parser/puml_parser/tests/sequence_diagram/return_commands/return_commands.puml
new file mode 100644
index 00000000..c2063002
--- /dev/null
+++ b/plantuml/parser/puml_parser/tests/sequence_diagram/return_commands/return_commands.puml
@@ -0,0 +1,20 @@
+' *******************************************************************************
+' Copyright (c) 2026 Contributors to the Eclipse Foundation
+'
+' See the NOTICE file(s) distributed with this work for additional
+' information regarding copyright ownership.
+'
+' This program and the accompanying materials are made available under the
+' terms of the Apache License Version 2.0 which is available at
+' https://www.apache.org/licenses/LICENSE-2.0
+'
+' SPDX-License-Identifier: Apache-2.0
+' *******************************************************************************
+@startuml return_commands
+
+Alice -> Bob : call
+return done
+Alice -> Bob : ping
+return Result>>
+
+@enduml
diff --git a/plantuml/parser/puml_resolver/src/sequence_diagram/src/logic_parser.rs b/plantuml/parser/puml_resolver/src/sequence_diagram/src/logic_parser.rs
index ddd92617..f41d0e46 100644
--- a/plantuml/parser/puml_resolver/src/sequence_diagram/src/logic_parser.rs
+++ b/plantuml/parser/puml_resolver/src/sequence_diagram/src/logic_parser.rs
@@ -28,20 +28,16 @@ fn endpoint_name(endpoint: &MessageEndpoint) -> String {
}
}
-/// Convert a syntax-level `GroupType` into the metamodel `ConditionType`.
-fn group_type_to_condition(gt: &GroupType) -> ConditionType {
- match gt {
- GroupType::Opt => ConditionType::Opt,
- GroupType::Alt => ConditionType::Alt,
- GroupType::Loop => ConditionType::Loop,
- GroupType::Par => ConditionType::Par,
- GroupType::Par2 => ConditionType::Par2,
- GroupType::Break => ConditionType::Break,
- GroupType::Critical => ConditionType::Critical,
- GroupType::Else => ConditionType::Else,
- GroupType::Also => ConditionType::Also,
- GroupType::End => ConditionType::End,
- GroupType::Group => ConditionType::Group,
+/// Convert a syntax-level `GroupKind` into the metamodel `ConditionType`.
+fn group_kind_to_condition(kind: &GroupKind) -> ConditionType {
+ match kind {
+ GroupKind::Opt => ConditionType::Opt,
+ GroupKind::Alt => ConditionType::Alt,
+ GroupKind::Loop => ConditionType::Loop,
+ GroupKind::Par => ConditionType::Par,
+ GroupKind::Break => ConditionType::Break,
+ GroupKind::Critical => ConditionType::Critical,
+ GroupKind::Group => ConditionType::Group,
}
}
@@ -55,12 +51,9 @@ pub fn build_tree(statements: &[Statement]) -> Vec {
nodes.push(node);
i += consumed;
} else {
- // Skip over else/also/end that are not handled
+ // Skip over branch/end markers that are not handled
if let Some(Statement::GroupCmd(g)) = statements.get(i) {
- if matches!(
- g.group_type,
- GroupType::Else | GroupType::Also | GroupType::End
- ) {
+ if matches!(g, GroupCmd::Else(_) | GroupCmd::End(_)) {
i += 1;
continue;
}
@@ -77,20 +70,8 @@ pub(crate) fn box_nodes(nodes: Vec) -> Vec {
nodes
}
-fn is_group_start(group_type: &GroupType) -> bool {
- matches!(
- group_type,
- GroupType::Alt
- | GroupType::Opt
- | GroupType::Loop
- | GroupType::Par
- | GroupType::Par2
- | GroupType::Break
- | GroupType::Critical
- | GroupType::Group
- | GroupType::Else
- | GroupType::Also
- )
+fn is_group_node(group: &GroupCmd) -> bool {
+ matches!(group, GroupCmd::Start(_) | GroupCmd::Else(_))
}
fn collect_group_statements(statements: &[Statement]) -> (Vec, usize) {
@@ -100,8 +81,8 @@ fn collect_group_statements(statements: &[Statement]) -> (Vec, usize)
for stmt in &statements[1..] {
if let Statement::GroupCmd(group) = stmt {
- match group.group_type {
- GroupType::End => {
+ match group {
+ GroupCmd::End(_) => {
if nesting_depth > 0 {
nesting_depth -= 1;
group_statements.push(stmt.clone());
@@ -109,20 +90,17 @@ fn collect_group_statements(statements: &[Statement]) -> (Vec, usize)
break;
}
}
- GroupType::Else | GroupType::Also => {
+ GroupCmd::Else(_) => {
if nesting_depth > 0 {
group_statements.push(stmt.clone());
} else {
break;
}
}
- _ if is_group_start(&group.group_type) => {
+ GroupCmd::Start(_) => {
nesting_depth += 1;
group_statements.push(stmt.clone());
}
- _ => {
- group_statements.push(stmt.clone());
- }
}
} else {
group_statements.push(stmt.clone());
@@ -134,22 +112,45 @@ fn collect_group_statements(statements: &[Statement]) -> (Vec, usize)
}
fn build_group_node(statements: &[Statement], group: &GroupCmd) -> (SequenceNode, usize) {
- let condition = Condition {
- condition_type: group_type_to_condition(&group.group_type),
- condition_value: group.text.clone().unwrap_or_default(),
- };
+ let (condition, source_location) = group_condition_and_location(group);
let (group_statements, consumed) = collect_group_statements(statements);
(
SequenceNode {
event: Event::Condition(condition),
- source_location: group.source_location.clone(),
+ source_location,
branches_node: box_nodes(build_tree(&group_statements)),
},
consumed,
)
}
+fn group_condition_and_location(group: &GroupCmd) -> (Condition, SourceLocation) {
+ match group {
+ GroupCmd::Start(start) => (
+ Condition {
+ condition_type: group_kind_to_condition(&start.kind),
+ condition_value: start.label.clone().unwrap_or_default(),
+ },
+ start.source_location.clone(),
+ ),
+ GroupCmd::Else(else_cmd) => (
+ Condition {
+ condition_type: ConditionType::Else,
+ condition_value: else_cmd.label.clone().unwrap_or_default(),
+ },
+ else_cmd.source_location.clone(),
+ ),
+ GroupCmd::End(end) => (
+ Condition {
+ condition_type: ConditionType::End,
+ condition_value: String::new(),
+ },
+ end.source_location.clone(),
+ ),
+ }
+}
+
/// Build a single sequence node and return how many statements were consumed
fn build_node(statements: &[Statement]) -> Option<(SequenceNode, usize)> {
if statements.is_empty() {
@@ -235,11 +236,9 @@ fn build_node(statements: &[Statement]) -> Option<(SequenceNode, usize)> {
}
Statement::GroupCmd(group) => {
// Handle group commands (alt, opt, loop, else, etc.)
- match group.group_type {
- GroupType::End => {
- None // End markers signal the close of a branch
- }
- _ if is_group_start(&group.group_type) => Some(build_group_node(statements, group)),
+ match group {
+ GroupCmd::End(_) => None, // End markers signal the close of a branch
+ _ if is_group_node(group) => Some(build_group_node(statements, group)),
_ => None,
}
}
diff --git a/plantuml/parser/puml_resolver/src/sequence_diagram/src/sequence_resolver.rs b/plantuml/parser/puml_resolver/src/sequence_diagram/src/sequence_resolver.rs
index f70be12e..5749cf9b 100644
--- a/plantuml/parser/puml_resolver/src/sequence_diagram/src/sequence_resolver.rs
+++ b/plantuml/parser/puml_resolver/src/sequence_diagram/src/sequence_resolver.rs
@@ -17,7 +17,7 @@ use sequence_logic::{
ParticipantType as LogicParticipantType, SequenceParticipant, SequenceTree, SourceLocation,
};
use sequence_parser::sequence_ast::{
- MessageEndpoint, ParticipantDef, ParticipantIdentifier,
+ CreateCmd, MessageEndpoint, ParticipantDef, ParticipantIdentifier,
ParticipantType as SyntaxParticipantType, Statement,
};
use sequence_parser::SeqPumlDocument;
@@ -76,12 +76,22 @@ fn add_explicit_participants(
resolved_names: &mut HashSet,
) {
for stmt in statements {
- if let Statement::ParticipantDef(participant_def) = stmt {
- add_participant(
- participants,
- resolved_names,
- explicit_participant(participant_def),
- );
+ match stmt {
+ Statement::ParticipantDef(participant_def) => {
+ add_participant(
+ participants,
+ resolved_names,
+ explicit_participant(participant_def),
+ );
+ }
+ Statement::CreateCmd(create_cmd) => {
+ add_participant(
+ participants,
+ resolved_names,
+ created_participant(create_cmd),
+ );
+ }
+ _ => {}
}
}
}
@@ -150,6 +160,16 @@ fn explicit_participant(participant_def: &ParticipantDef) -> SequenceParticipant
}
}
+fn created_participant(create_cmd: &CreateCmd) -> SequenceParticipant {
+ SequenceParticipant {
+ display_name: create_cmd.identifier.display_name.clone(),
+ alias: create_cmd.identifier.alias.clone(),
+ participant_type: map_parser_participant_type(&create_cmd.participant_type),
+ source_location: create_cmd.source_location.clone(),
+ stereotype: create_cmd.stereotype.clone(),
+ }
+}
+
fn implicit_participant(
identifier: &ParticipantIdentifier,
source_location: &SourceLocation,
@@ -315,7 +335,6 @@ mod sequence_resolver_tests {
fn make_participant(name: &str) -> Statement {
Statement::ParticipantDef(ParticipantDef {
- is_create: false,
participant_type: SyntaxParticipantType::Participant,
identifier: ParticipantIdentifier {
display_name: name.to_string(),
@@ -328,7 +347,6 @@ mod sequence_resolver_tests {
fn make_participant_with_alias(display_name: &str, alias: &str) -> Statement {
Statement::ParticipantDef(ParticipantDef {
- is_create: false,
participant_type: SyntaxParticipantType::Participant,
identifier: ParticipantIdentifier {
display_name: display_name.to_string(),