| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250 |
- // 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())
- }
|