Skip to content

Commit df8dec0

Browse files
melodyoncodehoe-jo
authored andcommitted
refactor sequence message -- parser part
1 parent 1e21640 commit df8dec0

9 files changed

Lines changed: 529 additions & 89 deletions

File tree

‎plantuml/parser/puml_parser/src/grammar/sequence.pest‎

Lines changed: 36 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -168,38 +168,58 @@ sequence_arrow = {
168168

169169
// Messages
170170
message = {
171-
async_marker? ~
172-
message_participant? ~ sequence_arrow ~ message_participant? ~
173-
activation_marker? ~ sequence_description?
171+
parallel_marker? ~
172+
message_endpoint ~ sequence_arrow ~ message_endpoint ~
173+
message_suffix? ~ sequence_description?
174174
}
175175

176-
async_marker = { "&" }
177-
message_participant = {
178-
lost_found_marker |
179-
quoted_display_with_alias |
180-
display_with_alias |
181-
alias_with_quoted_display |
182-
participant_ref |
183-
quoted_string
176+
parallel_marker = { "&" }
177+
message_endpoint = {
178+
lost_found_marker
179+
| inline_participant
180+
}
181+
182+
inline_participant = {
183+
quoted_display_with_alias
184+
| alias_with_quoted_display
185+
| quoted_display
186+
| alias_only
184187
}
185-
lost_found_marker = { "[" ~ ("o" | "x")? | ("o" | "x")? ~ ("[" | "]") }
186-
participant_ref = { CNAME | quoted_string }
187188

188-
activation_marker = { "+"+ | "-"+ | "*"+ | "!"+ }
189+
lost_found_marker = {
190+
left_lost_found
191+
| right_lost_found
192+
}
193+
// '[', '[o', '[x'
194+
left_lost_found = { "[" ~ ("o" | "x")? }
195+
// ']', 'o]', 'x]'
196+
right_lost_found = { ("o" | "x")? ~ "]" }
197+
198+
message_suffix = {
199+
activate_suffix
200+
| deactivate_suffix
201+
| create_suffix
202+
| destroy_suffix
203+
}
204+
activate_suffix = { "++" ~ color_spec? }
205+
deactivate_suffix = { "--" }
206+
create_suffix = { "**" }
207+
destroy_suffix = { "!!" }
189208

190209
// Short activation syntax
191210
activation_short = { participant_ref ~ ("++" | "--") }
211+
participant_ref = { CNAME | quoted_string }
192212

193213
// Return
194-
return_cmd = { async_marker? ~ ^"return" ~ sequence_text_content? }
214+
return_cmd = { parallel_marker? ~ ^"return" ~ sequence_text_content? }
195215

196216
// Box
197217
box_start = { "box" ~ quoted_string? ~ color_spec? }
198218
box_end = { ^"end" ~ WHITESPACE? ~ ^"box" }
199219

200220
// Group commands (alt, opt, loop, etc.)
201221
group_cmd = {
202-
async_marker? ~ group_type ~ group_condition?
222+
parallel_marker? ~ group_type ~ group_condition?
203223
}
204224

205225
group_condition = @{ (!EOL ~ ANY)+ }

‎plantuml/parser/puml_parser/src/sequence_diagram/src/lib.rs‎

Lines changed: 3 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -15,26 +15,9 @@ pub mod sequence_ast;
1515
mod sequence_parser;
1616

1717
pub use sequence_ast::{
18-
ActivateCmd, Arrow, CreateCmd, DeactivateCmd, DestroyCmd, ExternalEndpoint, GroupCmd,
19-
GroupType, Message, MessageContent, ParticipantDef, ParticipantIdentifier, ParticipantType,
20-
SeqPumlDocument, Statement,
18+
ActivateCmd, Arrow, CreateCmd, DeactivateCmd, DestroyCmd, GroupCmd, GroupType, Message,
19+
MessageEndpoint, MessageSuffix, ParticipantIdentifier, ParticipantType, SeqPumlDocument,
20+
Statement,
2121
};
2222

2323
pub use sequence_parser::{PumlSequenceParser, SequenceError};
24-
25-
/// Parse a PlantUML sequence diagram and return the document name and statements
26-
/// This is a convenience function for backwards compatibility with tests
27-
pub fn parse_sequence_diagram(
28-
input: &str,
29-
) -> Result<(Option<String>, Vec<Statement>), Box<dyn std::error::Error>> {
30-
use parser_core::DiagramParser;
31-
use puml_utils::LogLevel;
32-
use std::path::PathBuf;
33-
use std::rc::Rc;
34-
35-
let mut parser = PumlSequenceParser;
36-
let dummy_path = Rc::new(PathBuf::from("<input>"));
37-
let document = parser.parse_file(&dummy_path, input, LogLevel::Error)?;
38-
39-
Ok((document.name, document.statements))
40-
}

‎plantuml/parser/puml_parser/src/sequence_diagram/src/sequence_ast.rs‎

Lines changed: 18 additions & 30 deletions
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,6 @@
1414

1515
use serde::{Deserialize, Serialize};
1616
use source_location::SourceLocation;
17-
use std::str::FromStr;
1817

1918
pub use parser_core::common_ast::Arrow;
2019

@@ -27,6 +26,9 @@ pub struct SeqPumlDocument {
2726

2827
// Statement types used during parsing
2928
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
29+
// Keep `Message` unboxed for now because the statement representation is
30+
// expected to be revisited as the sequence parser/resolver model settles.
31+
#[allow(clippy::large_enum_variant)]
3032
pub enum Statement {
3133
DestroyCmd(DestroyCmd),
3234
CreateCmd(CreateCmd),
@@ -48,26 +50,6 @@ pub struct ParticipantDef {
4850
pub source_location: SourceLocation,
4951
}
5052

51-
#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
52-
pub struct ExternalEndpoint;
53-
54-
impl FromStr for ExternalEndpoint {
55-
type Err = ();
56-
57-
fn from_str(value: &str) -> Result<Self, Self::Err> {
58-
match value {
59-
"[" | "[o" | "[x" | "]" | "o]" | "x]" => Ok(ExternalEndpoint),
60-
_ => Err(()),
61-
}
62-
}
63-
}
64-
65-
impl ExternalEndpoint {
66-
pub fn as_name(self) -> &'static str {
67-
"ExternalEndpoint"
68-
}
69-
}
70-
7153
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
7254
pub enum ParticipantType {
7355
Participant,
@@ -111,20 +93,26 @@ pub struct DeactivateCmd {
11193
// Messages (internal parsing structure)
11294
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
11395
pub struct Message {
114-
pub content: MessageContent,
115-
#[serde(skip_serializing_if = "Option::is_none")]
116-
pub activation_marker: Option<String>,
96+
pub left: MessageEndpoint,
97+
pub arrow: Arrow,
98+
pub right: MessageEndpoint,
99+
pub suffix: Option<MessageSuffix>,
117100
pub description: Option<String>,
118101
pub source_location: SourceLocation,
119102
}
120103

121104
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
122-
pub enum MessageContent {
123-
WithTargets {
124-
left: String,
125-
arrow: Arrow,
126-
right: String,
127-
},
105+
pub enum MessageEndpoint {
106+
Participant(ParticipantIdentifier),
107+
LostFound(String),
108+
}
109+
110+
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
111+
pub enum MessageSuffix {
112+
Activate, // ++
113+
Deactivate, // --
114+
Create, // **
115+
Destroy, // !!
128116
}
129117

130118
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]

‎plantuml/parser/puml_parser/src/sequence_diagram/src/sequence_parser.rs‎

Lines changed: 68 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -243,28 +243,28 @@ impl PumlSequenceParser {
243243
pair: pest::iterators::Pair<Rule>,
244244
source_location: SourceLocation,
245245
) -> Result<Message, SequenceError> {
246-
let mut left: Option<String> = None;
246+
let mut left: Option<MessageEndpoint> = None;
247247
let mut arrow: Option<Arrow> = None;
248-
let mut right: Option<String> = None;
249-
let mut activation_marker: Option<String> = None;
248+
let mut right: Option<MessageEndpoint> = None;
249+
let mut suffix: Option<MessageSuffix> = None;
250250
let mut description: Option<String> = None;
251251

252252
for inner in pair.into_inner() {
253253
match inner.as_rule() {
254-
Rule::message_participant => {
255-
let participant = Self::extract_participant_ref(inner);
254+
Rule::message_endpoint => {
255+
let endpoint = Self::parse_message_endpoint(inner)?;
256256
// First participant goes to left, second to right
257257
if arrow.is_none() {
258-
left = Some(participant);
258+
left = Some(endpoint);
259259
} else {
260-
right = Some(participant);
260+
right = Some(endpoint);
261261
}
262262
}
263263
Rule::sequence_arrow => {
264264
arrow = Some(Self::parse_arrow(inner)?);
265265
}
266-
Rule::activation_marker => {
267-
activation_marker = Some(inner.as_str().to_string());
266+
Rule::message_suffix => {
267+
suffix = Some(Self::parse_message_suffix(inner));
268268
}
269269
Rule::sequence_description => {
270270
description = inner
@@ -276,22 +276,73 @@ impl PumlSequenceParser {
276276
}
277277
}
278278

279-
let content = MessageContent::WithTargets {
280-
left: left.unwrap_or_default(),
279+
Ok(Message {
280+
left: left.expect("message must contain left endpoint"),
281281
arrow: arrow.ok_or_else(|| {
282282
SequenceError::InvalidStatement("missing arrow in message".to_string())
283283
})?,
284-
right: right.unwrap_or_default(),
285-
};
286-
287-
Ok(Message {
288-
content,
289-
activation_marker,
284+
right: right.expect("message must contain right endpoint"),
285+
suffix,
290286
description,
291287
source_location,
292288
})
293289
}
294290

291+
fn parse_message_endpoint(
292+
pair: pest::iterators::Pair<Rule>,
293+
) -> Result<MessageEndpoint, SequenceError> {
294+
let endpoint = pair
295+
.into_inner()
296+
.next()
297+
.expect("message_endpoint must contain an endpoint");
298+
299+
Ok(match endpoint.as_rule() {
300+
Rule::inline_participant => {
301+
MessageEndpoint::Participant(Self::parse_participant_identifier(endpoint)?)
302+
}
303+
Rule::lost_found_marker => {
304+
MessageEndpoint::LostFound(Self::parse_lost_found_marker(endpoint))
305+
}
306+
_ => unreachable!(
307+
"message_endpoint grammar produced unsupported value: {:?}",
308+
endpoint.as_rule()
309+
),
310+
})
311+
}
312+
313+
fn parse_lost_found_marker(pair: pest::iterators::Pair<Rule>) -> String {
314+
let marker = pair
315+
.into_inner()
316+
.next()
317+
.expect("lost_found_marker must contain a lost-found endpoint");
318+
319+
match marker.as_rule() {
320+
Rule::left_lost_found | Rule::right_lost_found => marker.as_str().trim().to_string(),
321+
_ => unreachable!(
322+
"lost_found_marker grammar produced unsupported value: {:?}",
323+
marker.as_rule()
324+
),
325+
}
326+
}
327+
328+
fn parse_message_suffix(pair: pest::iterators::Pair<Rule>) -> MessageSuffix {
329+
let suffix = pair
330+
.into_inner()
331+
.next()
332+
.expect("message_suffix must contain a suffix");
333+
334+
match suffix.as_rule() {
335+
Rule::activate_suffix => MessageSuffix::Activate,
336+
Rule::deactivate_suffix => MessageSuffix::Deactivate,
337+
Rule::create_suffix => MessageSuffix::Create,
338+
Rule::destroy_suffix => MessageSuffix::Destroy,
339+
_ => unreachable!(
340+
"message_suffix grammar produced unsupported value: {:?}",
341+
suffix.as_rule()
342+
),
343+
}
344+
}
345+
295346
fn parse_arrow(pair: pest::iterators::Pair<Rule>) -> Result<Arrow, SequenceError> {
296347
common_parse_arrow(pair)
297348
.map_err(|e| SequenceError::InvalidStatement(format!("invalid arrow: {}", e)))
@@ -433,12 +484,6 @@ impl PumlSequenceParser {
433484

434485
fn extract_participant_ref(pair: pest::iterators::Pair<Rule>) -> String {
435486
match pair.as_rule() {
436-
Rule::message_participant => pair
437-
.into_inner()
438-
.next()
439-
.map(Self::extract_participant_ref)
440-
.unwrap_or_default(),
441-
442487
Rule::participant_ref => {
443488
let fallback = pair.as_str().trim();
444489

‎plantuml/parser/puml_parser/src/sequence_diagram/test/sequence_integration_test.rs‎

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -65,3 +65,13 @@ fn run_sequence_diagram_parser_case(case_name: &str) {
6565
fn test_participant_identifiers() {
6666
run_sequence_diagram_parser_case("participant_identifiers");
6767
}
68+
69+
#[test]
70+
fn test_message_participants() {
71+
run_sequence_diagram_parser_case("message_participants");
72+
}
73+
74+
#[test]
75+
fn test_message_lost_found() {
76+
run_sequence_diagram_parser_case("message_lost_found");
77+
}
Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
' *******************************************************************************
2+
' Copyright (c) 2026 Contributors to the Eclipse Foundation
3+
'
4+
' See the NOTICE file(s) distributed with this work for additional
5+
' information regarding copyright ownership.
6+
'
7+
' This program and the accompanying materials are made available under the
8+
' terms of the Apache License Version 2.0 which is available at
9+
' https://www.apache.org/licenses/LICENSE-2.0
10+
'
11+
' SPDX-License-Identifier: Apache-2.0
12+
' *******************************************************************************
13+
@startuml message_lost_found
14+
15+
[ -> Service : left lost bracket
16+
[o -> Service ++ : left lost bracket o
17+
[x -> Service ** : left lost bracket x
18+
Worker -> ] : right lost bracket
19+
Worker -> o] -- : right lost o bracket
20+
Worker -> x] !! : right lost x bracket
21+
22+
@enduml

0 commit comments

Comments
 (0)