// Package parser provides Organization Chart parsing package parser import ( "fmt" "strings" "mermaid-go/pkg/ast" "mermaid-go/pkg/lexer" ) // OrganizationParser implements Organization Chart parsing type OrganizationParser struct { tokens []lexer.Token current int diagram *ast.OrganizationDiagram } // NewOrganizationParser creates a new Organization parser func NewOrganizationParser() *OrganizationParser { return &OrganizationParser{ diagram: ast.NewOrganizationDiagram(), } } // Parse parses Organization Chart syntax func (p *OrganizationParser) Parse(input string) (*ast.OrganizationDiagram, 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.NewOrganizationDiagram() // Parse document err = p.parseDocument() if err != nil { return nil, fmt.Errorf("syntax analysis failed: %w", err) } return p.diagram, nil } // parseDocument parses the Organization Chart document func (p *OrganizationParser) parseDocument() error { // Expect organization or orgChart if !p.check(lexer.TokenID) || (p.peek().Value != "organization" && p.peek().Value != "orgChart") { return p.error("expected 'organization' or 'orgChart'") } p.advance() // Parse statements for !p.isAtEnd() { if err := p.parseStatement(); err != nil { return err } } // Build hierarchy after parsing all nodes p.buildHierarchy() return nil } // parseStatement parses individual Organization Chart statements func (p *OrganizationParser) 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.check(lexer.TokenID): // Node definition return p.parseNode() default: token := p.peek() return p.error(fmt.Sprintf("unexpected token: %s", token.Value)) } } // parseTitle parses title statements func (p *OrganizationParser) 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 } // parseNode parses node definitions func (p *OrganizationParser) parseNode() error { // Parse node ID nodeID := p.advance().Value node := &ast.OrganizationNode{ ID: nodeID, Name: nodeID, // Default name is ID Level: 0, // Will be calculated later Children: make([]*ast.OrganizationNode, 0), CssClasses: make([]string, 0), Styles: make([]string, 0), } // Parse node properties for !p.check(lexer.TokenNewline) && !p.isAtEnd() { if p.check(lexer.TokenOpenBracket) { p.advance() // consume '[' // Parse node title/name var titleParts []string for !p.check(lexer.TokenCloseBracket) && !p.isAtEnd() { titleParts = append(titleParts, p.advance().Value) } if len(titleParts) > 0 { title := strings.TrimSpace(strings.Join(titleParts, " ")) node.Name = title } if p.check(lexer.TokenCloseBracket) { p.advance() // consume ']' } } else if p.check(lexer.TokenArrowSolid) || p.check(lexer.TokenMinus) { // Parse relationship p.advance() // consume arrow or minus // Skip additional arrow characters for p.check(lexer.TokenMinus) || p.check(lexer.TokenArrowSolid) { p.advance() } // Parse child node if p.check(lexer.TokenID) { childID := p.advance().Value // Create or find child node childNode := p.diagram.FindNode(childID) if childNode == nil { childNode = &ast.OrganizationNode{ ID: childID, Name: childID, Level: node.Level + 1, Children: make([]*ast.OrganizationNode, 0), CssClasses: make([]string, 0), Styles: make([]string, 0), } p.diagram.AddNode(childNode) } // Establish parent-child relationship node.AddChild(childNode) } } else { p.advance() // consume unknown token } } p.diagram.AddNode(node) return nil } // buildHierarchy builds the hierarchical structure func (p *OrganizationParser) buildHierarchy() { // Find root nodes (nodes without parents) for _, node := range p.diagram.Nodes { if node.Parent == nil && p.diagram.Root == nil { p.diagram.Root = node break } } // Calculate levels if p.diagram.Root != nil { p.calculateLevels(p.diagram.Root, 0) } } // calculateLevels recursively calculates node levels func (p *OrganizationParser) calculateLevels(node *ast.OrganizationNode, level int) { node.Level = level for _, child := range node.Children { p.calculateLevels(child, level+1) } } // Helper methods func (p *OrganizationParser) check(tokenType lexer.TokenType) bool { if p.isAtEnd() { return false } return p.peek().Type == tokenType } func (p *OrganizationParser) 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 *OrganizationParser) advance() lexer.Token { if !p.isAtEnd() { p.current++ } return p.previous() } func (p *OrganizationParser) isAtEnd() bool { return p.current >= len(p.tokens) || p.peek().Type == lexer.TokenEOF } func (p *OrganizationParser) peek() lexer.Token { if p.current >= len(p.tokens) { return lexer.Token{Type: lexer.TokenEOF} } return p.tokens[p.current] } func (p *OrganizationParser) previous() lexer.Token { if p.current <= 0 { return lexer.Token{Type: lexer.TokenEOF} } return p.tokens[p.current-1] } func (p *OrganizationParser) 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()) }