merman-core 0.9.2

Mermaid parser + semantic model (headless; parity-focused).
Documentation
// 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(),
};