| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390 |
- // Package parser provides BPMN parsing
- package parser
- import (
- "fmt"
- "strings"
- "mermaid-go/pkg/ast"
- "mermaid-go/pkg/lexer"
- )
- // BPMNParser implements BPMN parsing
- type BPMNParser struct {
- tokens []lexer.Token
- current int
- diagram *ast.BPMNDiagram
- }
- // NewBPMNParser creates a new BPMN parser
- func NewBPMNParser() *BPMNParser {
- return &BPMNParser{
- diagram: ast.NewBPMNDiagram(),
- }
- }
- // Parse parses BPMN syntax
- func (p *BPMNParser) Parse(input string) (*ast.BPMNDiagram, error) {
- // Tokenize
- l := lexer.NewLexer(input)
- tokens, err := l.Tokenize()
- if err != nil {
- return nil, fmt.Errorf("lexical analysis failed: %w", err)
- }
- // Filter tokens
- p.tokens = lexer.FilterTokens(tokens)
- p.current = 0
- p.diagram = ast.NewBPMNDiagram()
- // Parse document
- err = p.parseDocument()
- if err != nil {
- return nil, fmt.Errorf("syntax analysis failed: %w", err)
- }
- return p.diagram, nil
- }
- // parseDocument parses the BPMN document
- func (p *BPMNParser) parseDocument() error {
- // Expect bpmn
- if !p.check(lexer.TokenID) || p.peek().Value != "bpmn" {
- return p.error("expected 'bpmn'")
- }
- p.advance()
- // Parse statements
- for !p.isAtEnd() {
- if err := p.parseStatement(); err != nil {
- return err
- }
- }
- return nil
- }
- // parseStatement parses individual BPMN statements
- func (p *BPMNParser) parseStatement() error {
- if p.isAtEnd() {
- return nil
- }
- switch {
- case p.check(lexer.TokenNewline):
- p.advance() // Skip newlines
- return nil
- case p.checkKeyword("title"):
- return p.parseTitle()
- case p.checkKeyword("pool"):
- return p.parsePool()
- case p.checkKeyword("lane"):
- return p.parseLane()
- case p.check(lexer.TokenID):
- // Element definition or flow
- return p.parseElementOrFlow()
- default:
- token := p.peek()
- return p.error(fmt.Sprintf("unexpected token: %s", token.Value))
- }
- }
- // parseTitle parses title statements
- func (p *BPMNParser) parseTitle() error {
- p.advance() // consume 'title'
- var titleParts []string
- for !p.check(lexer.TokenNewline) && !p.isAtEnd() {
- titleParts = append(titleParts, p.advance().Value)
- }
- if len(titleParts) > 0 {
- title := strings.TrimSpace(strings.Join(titleParts, " "))
- p.diagram.Title = &title
- }
- return nil
- }
- // parsePool parses pool statements
- func (p *BPMNParser) parsePool() error {
- p.advance() // consume 'pool'
- if !p.check(lexer.TokenID) {
- return p.error("expected pool ID")
- }
- poolID := p.advance().Value
- pool := &ast.BPMNPool{
- ID: poolID,
- Name: poolID,
- Lanes: make([]string, 0),
- }
- // Parse pool properties
- for !p.check(lexer.TokenNewline) && !p.isAtEnd() {
- if p.check(lexer.TokenOpenBracket) {
- p.advance() // consume '['
- // Parse pool name
- var nameParts []string
- for !p.check(lexer.TokenCloseBracket) && !p.isAtEnd() {
- nameParts = append(nameParts, p.advance().Value)
- }
- if len(nameParts) > 0 {
- pool.Name = strings.TrimSpace(strings.Join(nameParts, " "))
- }
- if p.check(lexer.TokenCloseBracket) {
- p.advance() // consume ']'
- }
- } else {
- p.advance() // consume unknown token
- }
- }
- p.diagram.Pools = append(p.diagram.Pools, pool)
- return nil
- }
- // parseLane parses lane statements
- func (p *BPMNParser) parseLane() error {
- p.advance() // consume 'lane'
- if !p.check(lexer.TokenID) {
- return p.error("expected lane ID")
- }
- laneID := p.advance().Value
- lane := &ast.BPMNLane{
- ID: laneID,
- Name: laneID,
- Elements: make([]string, 0),
- }
- // Parse lane properties
- for !p.check(lexer.TokenNewline) && !p.isAtEnd() {
- if p.check(lexer.TokenOpenBracket) {
- p.advance() // consume '['
- // Parse lane name
- var nameParts []string
- for !p.check(lexer.TokenCloseBracket) && !p.isAtEnd() {
- nameParts = append(nameParts, p.advance().Value)
- }
- if len(nameParts) > 0 {
- lane.Name = strings.TrimSpace(strings.Join(nameParts, " "))
- }
- if p.check(lexer.TokenCloseBracket) {
- p.advance() // consume ']'
- }
- } else if p.checkKeyword("in") {
- p.advance() // consume 'in'
- if p.check(lexer.TokenID) {
- lane.Pool = p.advance().Value
- }
- } else {
- p.advance() // consume unknown token
- }
- }
- p.diagram.Lanes = append(p.diagram.Lanes, lane)
- return nil
- }
- // parseElementOrFlow parses element definition or flow
- func (p *BPMNParser) parseElementOrFlow() error {
- elementID := p.advance().Value
- // Check if this is a flow (has arrow indicators)
- if p.checkFlow() {
- return p.parseFlow(elementID)
- }
- // Otherwise, it's an element definition
- element := &ast.BPMNElement{
- ID: elementID,
- Name: elementID,
- Type: p.inferElementType(elementID),
- Properties: make(map[string]any),
- CssClasses: make([]string, 0),
- }
- // Parse element properties
- for !p.check(lexer.TokenNewline) && !p.isAtEnd() {
- if p.check(lexer.TokenOpenBracket) {
- p.advance() // consume '['
- // Parse element name
- var nameParts []string
- for !p.check(lexer.TokenCloseBracket) && !p.isAtEnd() {
- nameParts = append(nameParts, p.advance().Value)
- }
- if len(nameParts) > 0 {
- element.Name = strings.TrimSpace(strings.Join(nameParts, " "))
- }
- if p.check(lexer.TokenCloseBracket) {
- p.advance() // consume ']'
- }
- } else if p.check(lexer.TokenOpenParen) {
- // Parse element type
- p.advance() // consume '('
- if p.check(lexer.TokenID) {
- typeStr := p.advance().Value
- element.Type = ast.BPMNElementType(typeStr)
- }
- if p.check(lexer.TokenCloseParen) {
- p.advance() // consume ')'
- }
- } else {
- p.advance() // consume unknown token
- }
- }
- p.diagram.AddElement(element)
- return nil
- }
- // parseFlow parses flow connections
- func (p *BPMNParser) parseFlow(fromID string) error {
- // Parse flow type and direction
- flowType := ast.BPMNFlowSequence // default
- // Skip flow indicators
- for p.checkFlow() {
- token := p.advance()
- if token.Value == "-->" {
- flowType = ast.BPMNFlowSequence
- } else if token.Value == "-.>" {
- flowType = ast.BPMNFlowMessage
- }
- }
- // Parse target element
- if !p.check(lexer.TokenID) {
- return p.error("expected target element ID")
- }
- toID := p.advance().Value
- flow := &ast.BPMNFlow{
- ID: fmt.Sprintf("%s_to_%s", fromID, toID),
- From: fromID,
- To: toID,
- Type: flowType,
- Properties: make(map[string]any),
- }
- // Parse flow label
- if p.check(lexer.TokenColon) {
- p.advance() // consume ':'
- var labelParts []string
- for !p.check(lexer.TokenNewline) && !p.isAtEnd() {
- labelParts = append(labelParts, p.advance().Value)
- }
- if len(labelParts) > 0 {
- label := strings.TrimSpace(strings.Join(labelParts, " "))
- flow.Name = &label
- }
- }
- p.diagram.AddFlow(flow)
- return nil
- }
- // checkFlow checks if current position looks like a flow
- func (p *BPMNParser) checkFlow() bool {
- if p.isAtEnd() {
- return false
- }
- token := p.peek()
- return token.Type == lexer.TokenArrowSolid ||
- token.Type == lexer.TokenArrowDotted ||
- token.Type == lexer.TokenMinus
- }
- // inferElementType infers BPMN element type from ID
- func (p *BPMNParser) inferElementType(id string) ast.BPMNElementType {
- lowerID := strings.ToLower(id)
- // Infer type from common naming patterns
- if strings.Contains(lowerID, "start") {
- return ast.BPMNElementStartEvent
- }
- if strings.Contains(lowerID, "end") {
- return ast.BPMNElementEndEvent
- }
- if strings.Contains(lowerID, "gateway") || strings.Contains(lowerID, "decision") {
- return ast.BPMNElementExclusiveGateway
- }
- if strings.Contains(lowerID, "task") {
- return ast.BPMNElementTask
- }
- if strings.Contains(lowerID, "user") {
- return ast.BPMNElementUserTask
- }
- if strings.Contains(lowerID, "service") {
- return ast.BPMNElementServiceTask
- }
- // Default to task
- return ast.BPMNElementTask
- }
- // Helper methods
- func (p *BPMNParser) check(tokenType lexer.TokenType) bool {
- if p.isAtEnd() {
- return false
- }
- return p.peek().Type == tokenType
- }
- func (p *BPMNParser) checkKeyword(keyword string) bool {
- if p.isAtEnd() {
- return false
- }
- token := p.peek()
- return token.Type == lexer.TokenID && strings.ToLower(token.Value) == strings.ToLower(keyword)
- }
- func (p *BPMNParser) advance() lexer.Token {
- if !p.isAtEnd() {
- p.current++
- }
- return p.previous()
- }
- func (p *BPMNParser) isAtEnd() bool {
- return p.current >= len(p.tokens) || p.peek().Type == lexer.TokenEOF
- }
- func (p *BPMNParser) peek() lexer.Token {
- if p.current >= len(p.tokens) {
- return lexer.Token{Type: lexer.TokenEOF}
- }
- return p.tokens[p.current]
- }
- func (p *BPMNParser) previous() lexer.Token {
- if p.current <= 0 {
- return lexer.Token{Type: lexer.TokenEOF}
- }
- return p.tokens[p.current-1]
- }
- func (p *BPMNParser) error(message string) error {
- token := p.peek()
- return fmt.Errorf("parse error at line %d, column %d: %s (got %s)",
- token.Line, token.Column, message, token.Type.String())
- }
|