// Flowchart minimal grammar (phase 1).
grammar(statements: &mut StatementArena, control: &crate::OperationControl);
use crate::diagrams::flowchart::{
ArrowToken, ClassAssignStmt, ClassDefStmt, ClickStmt, DirectionStatementToken, Edge, FlowchartAst,
FlowNodeProvenance, FlowNodeSyntax, LabeledText, LinkStyleStmt, LinkToken, Node, NodeLabelToken,
StatementArena, StatementId, StatementList, Stmt, StyleStmt, SubgraphBlock, SubgraphHeader, TitleKind, Tok
};
use crate::SourceSpan;
extern {
type Location = usize;
type Error = crate::diagrams::flowchart::LexError;
enum Tok {
"graph" => Tok::KwGraph,
"flowchart" => Tok::KwFlowchart,
"flowchart-elk" => Tok::KwFlowchartElk,
"swimlane-beta" => Tok::KwSwimlane,
"subgraph" => Tok::KwSubgraph,
"end" => Tok::KwEnd,
Sep => Tok::Sep,
Amp => Tok::Amp,
StyleSep => Tok::StyleSep,
Direction => Tok::Direction(<String>),
DirectionStmt => Tok::DirectionStmt(<DirectionStatementToken>),
Id => Tok::Id(<String>),
NodeLabel => Tok::NodeLabel(<NodeLabelToken>),
Arrow => Tok::Arrow(<ArrowToken>),
EdgeLabel => Tok::EdgeLabel(<LabeledText>),
SubgraphHeader => Tok::SubgraphHeader(<SubgraphHeader>),
StyleStmt => Tok::StyleStmt(<StyleStmt>),
ClassDefStmt => Tok::ClassDefStmt(<ClassDefStmt>),
ClassAssignStmt => Tok::ClassAssignStmt(<ClassAssignStmt>),
ClickStmt => Tok::ClickStmt(<ClickStmt>),
LinkStyleStmt => Tok::LinkStyleStmt(<LinkStyleStmt>),
EdgeId => Tok::EdgeId(<String>),
ShapeData => Tok::ShapeData(<String>),
}
}
pub FlowchartAst: FlowchartAst = {
<_lead:Seps> <h:Header> <_s:Seps> <st:Statements> => {
let (keyword, direction, header_span) = h;
FlowchartAst { keyword, direction, header_span, statements: std::mem::take(statements), root: st }
},
};
Header: (String, Option<String>, SourceSpan) = {
<l:@L> "graph" <d:Direction?> <r:@R> => ("graph".to_string(), d, SourceSpan::new(l, r)),
<l:@L> "flowchart" <d:Direction?> <r:@R> => ("flowchart".to_string(), d, SourceSpan::new(l, r)),
<l:@L> "flowchart-elk" <d:Direction?> <r:@R> => ("flowchart-elk".to_string(), d, SourceSpan::new(l, r)),
<l:@L> "swimlane-beta" <d:Direction?> <r:@R> => ("swimlane-beta".to_string(), d, SourceSpan::new(l, r)),
};
Statements: StatementList = {
=> StatementList::default(),
<s:Statement> <rest:StatementRest> =>? statements.prepend(s, rest, control),
};
StatementRest: StatementList = {
<_sep:Sep> <_seps:Seps> <s:Statement> <rest:StatementRest> =>? statements.prepend(s, rest, control),
Seps => StatementList::default(),
};
Seps: () = {
=> (),
Sep Seps => (),
};
Statement: StatementId = {
<c:Chain> =>? statements.push(Stmt::Chain { node_groups: c.0, edge_groups: c.1 }, control),
<l:@L> <id:Id> <r:@R> <sd:ShapeData> =>? statements.push(Stmt::ShapeData {
target: id,
target_span: Some(SourceSpan::new(l, r)),
yaml: sd,
}, control),
<g:NodeGroupOnly> =>? statements.push(Stmt::Chain { node_groups: vec![g], edge_groups: Vec::new() }, control),
<n:NodeRef> =>? statements.push(Stmt::Node(Box::new(n)), control),
<sg:SubgraphBlock> =>? statements.push(Stmt::Subgraph(sg), control),
<d:DirectionStmt> =>? statements.push(Stmt::Direction(d.direction), control),
<s:StyleStmt> =>? statements.push(Stmt::Style(s), control),
<c:ClassDefStmt> =>? statements.push(Stmt::ClassDef(c), control),
<c:ClassAssignStmt> =>? statements.push(Stmt::ClassAssign(c), control),
<c:ClickStmt> =>? statements.push(Stmt::Click(c), control),
<ls:LinkStyleStmt> =>? statements.push(Stmt::LinkStyle(ls), control),
};
Chain: (Vec<Vec<Node>>, Vec<Vec<Edge>>) = {
<start:NodeGroup> <segs:EdgeSeg+> =>? {
let mut node_groups: Vec<Vec<Node>> = vec![start.clone()];
let mut edge_groups: Vec<Vec<Edge>> = Vec::new();
let mut prev_group = start;
for (eid, link, label, next_group) in segs {
let mut segment_edges: Vec<Edge> = Vec::new();
for from in &prev_group {
for to in &next_group {
statements.construction_checkpoint(control)?;
let is_last_start = from.id == prev_group[prev_group.len() - 1].id;
let is_first_end = to.id == next_group[0].id;
let edge_id = if is_last_start && is_first_end {
eid.clone()
} else {
None
};
let edge_label = label.as_ref().map(|l| l.text.clone());
let label_type = label.as_ref().map(|l| l.kind.clone()).unwrap_or(TitleKind::Text);
let label_span = label.as_ref().and_then(|l| l.span);
let label_selection = label.as_ref().and_then(|l| l.selection);
segment_edges.push(Edge {
from: from.id.clone(),
to: to.id.clone(),
id: edge_id,
link: link.clone(),
label: edge_label,
label_type,
label_span,
label_selection,
style: Vec::new(),
classes: Vec::new(),
interpolate: None,
is_user_defined_id: false,
animate: None,
animation: None,
});
}
}
edge_groups.push(segment_edges);
node_groups.push(next_group.clone());
prev_group = next_group;
}
Ok((node_groups, edge_groups))
}
};
Seps1: () = {
Sep Seps => (),
};
SubgraphBlock: SubgraphBlock = {
"subgraph" <h:SubgraphHeader?> <_s:Seps1> <inner:Statements> "end" => SubgraphBlock { header: h.unwrap_or_default(), statements: inner }
};
EdgeSeg: (Option<String>, LinkToken, Option<LabeledText>, Vec<Node>) = {
<eid:EdgeId?> <a:Arrow> <l:EdgeLabel?> <n:NodeGroup> => (eid, a.link, l, n),
};
NodeGroup: Vec<Node> = {
<first:NodeRefChain> <rest:NodeGroupRest*> => {
let mut v = vec![first];
v.extend(rest);
v
}
};
NodeGroupOnly: Vec<Node> = {
<first:NodeRefChain> <rest:NodeGroupRest+> => {
let mut v = vec![first];
v.extend(rest);
v
}
};
NodeGroupRest: Node = {
Amp <n:NodeRefChain> => n,
};
NodeRef: Node = {
<l:@L> <id:Id> <r:@R> <nl:NodeLabel> <sd:ShapeData?> <cls:ClassOpt> => {
let NodeLabelToken { shape, text, .. } = nl;
let label_span = text.span;
let label_selection = text.selection;
Node {
id_span: Some(SourceSpan::new(l, r)),
id,
provenance: FlowNodeProvenance::Authored,
syntax: FlowNodeSyntax::ExplicitDefinition,
label: Some(text.text),
label_type: text.kind,
label_span,
label_selection,
shape: Some(shape),
shape_data: sd,
icon: None,
form: None,
pos: None,
img: None,
constraint: None,
asset_width: None,
asset_height: None,
styles: Vec::new(),
classes: cls,
link: None,
link_target: None,
have_callback: false,
}
},
<l:@L> <id:Id> <r:@R> <cls:ClassOpt> => Node {
id_span: Some(SourceSpan::new(l, r)),
id,
provenance: FlowNodeProvenance::Authored,
syntax: if cls.is_empty() {
FlowNodeSyntax::BareReference
} else {
FlowNodeSyntax::ExplicitDefinition
},
label: None,
label_type: TitleKind::Text,
label_span: None,
label_selection: None,
shape: None,
shape_data: None,
icon: None,
form: None,
pos: None,
img: None,
constraint: None,
asset_width: None,
asset_height: None,
styles: Vec::new(),
classes: cls,
link: None,
link_target: None,
have_callback: false,
},
};
NodeRefChain: Node = {
<l:@L> <id:Id> <r:@R> <nl:NodeLabel> <sd:ShapeData?> <cls:ClassOpt> => {
let NodeLabelToken { shape, text, .. } = nl;
let label_span = text.span;
let label_selection = text.selection;
Node {
id_span: Some(SourceSpan::new(l, r)),
id,
provenance: FlowNodeProvenance::Authored,
syntax: FlowNodeSyntax::ExplicitDefinition,
label: Some(text.text),
label_type: text.kind,
label_span,
label_selection,
shape: Some(shape),
shape_data: sd,
icon: None,
form: None,
pos: None,
img: None,
constraint: None,
asset_width: None,
asset_height: None,
styles: Vec::new(),
classes: cls,
link: None,
link_target: None,
have_callback: false,
}
},
<l:@L> <id:Id> <r:@R> <sd:ShapeData> <cls:ClassOpt> => Node {
id_span: Some(SourceSpan::new(l, r)),
id,
provenance: FlowNodeProvenance::Authored,
syntax: FlowNodeSyntax::ExplicitDefinition,
label: None,
label_type: TitleKind::Text,
label_span: None,
label_selection: None,
shape: None,
shape_data: Some(sd),
icon: None,
form: None,
pos: None,
img: None,
constraint: None,
asset_width: None,
asset_height: None,
styles: Vec::new(),
classes: cls,
link: None,
link_target: None,
have_callback: false,
},
<l:@L> <id:Id> <r:@R> <cls:ClassOpt> => Node {
id_span: Some(SourceSpan::new(l, r)),
id,
provenance: FlowNodeProvenance::Authored,
syntax: if cls.is_empty() {
FlowNodeSyntax::BareReference
} else {
FlowNodeSyntax::ExplicitDefinition
},
label: None,
label_type: TitleKind::Text,
label_span: None,
label_selection: None,
shape: None,
shape_data: None,
icon: None,
form: None,
pos: None,
img: None,
constraint: None,
asset_width: None,
asset_height: None,
styles: Vec::new(),
classes: cls,
link: None,
link_target: None,
have_callback: false,
},
};
ClassOpt: Vec<String> = {
StyleSep <c:Id> => vec![c],
=> Vec::new(),
};