| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456 |
- // Package parser provides state diagram parsing based on stateDiagram.jison
- package parser
- import (
- "fmt"
- "strings"
- "mermaid-go/pkg/ast"
- "mermaid-go/pkg/lexer"
- )
- // StateParser implements state diagram parsing following stateDiagram.jison
- type StateParser struct {
- tokens []lexer.Token
- current int
- diagram *ast.StateDiagram
- }
- // NewStateParser creates a new state parser
- func NewStateParser() *StateParser {
- return &StateParser{
- diagram: ast.NewStateDiagram(),
- }
- }
- // Parse parses state diagram syntax
- func (p *StateParser) Parse(input string) (*ast.StateDiagram, 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.NewStateDiagram()
- // Parse document
- err = p.parseDocument()
- if err != nil {
- return nil, fmt.Errorf("syntax analysis failed: %w", err)
- }
- return p.diagram, nil
- }
- // parseDocument parses the state diagram document
- func (p *StateParser) parseDocument() error {
- // Expect stateDiagram or stateDiagram-v2
- if !p.check(lexer.TokenID) ||
- (p.peek().Value != "stateDiagram" && p.peek().Value != "stateDiagram-v2") {
- return p.error("expected 'stateDiagram' or 'stateDiagram-v2'")
- }
- p.advance()
- // Parse statements
- for !p.isAtEnd() {
- if err := p.parseStatement(); err != nil {
- return err
- }
- }
- return nil
- }
- // parseStatement parses individual state diagram statements
- func (p *StateParser) parseStatement() error {
- if p.isAtEnd() {
- return nil
- }
- switch {
- case p.check(lexer.TokenNewline):
- p.advance() // Skip newlines
- return nil
- case p.checkKeyword("direction"):
- return p.parseDirection()
- case p.checkKeyword("note"):
- return p.parseNote()
- case p.checkKeyword("state"):
- return p.parseState()
- case p.check(lexer.TokenOpenBracket):
- // Handle [*] start/end states
- return p.parseStartEndState()
- case p.check(lexer.TokenID):
- // Try to parse as state or transition
- return p.parseStateOrTransition()
- default:
- token := p.peek()
- return p.error(fmt.Sprintf("unexpected token: %s", token.Value))
- }
- }
- // parseState parses state declarations
- func (p *StateParser) parseState() error {
- p.advance() // consume 'state'
- if !p.check(lexer.TokenID) {
- return p.error("expected state name")
- }
- stateName := p.advance().Value
- state := &ast.StateNode{
- ID: stateName,
- Label: stateName,
- Type: ast.StateTypeDefault,
- SubStates: make(map[string]*ast.StateNode),
- CssClasses: make([]string, 0),
- }
- // Check for 'as' alias
- if p.checkKeyword("as") {
- p.advance() // consume 'as'
- if !p.check(lexer.TokenID) && !p.check(lexer.TokenString) {
- return p.error("expected state label after 'as'")
- }
- label := p.advance().Value
- if strings.HasPrefix(label, "\"") && strings.HasSuffix(label, "\"") {
- label = label[1 : len(label)-1] // Remove quotes
- }
- state.Label = label
- }
- // Check for state body (composite state)
- if p.check(lexer.TokenOpenBrace) {
- p.advance() // consume '{'
- err := p.parseStateBody(state)
- if err != nil {
- return err
- }
- if !p.check(lexer.TokenCloseBrace) {
- return p.error("expected '}'")
- }
- p.advance() // consume '}'
- }
- // Check for special state types
- if p.check(lexer.TokenColon) {
- p.advance() // consume ':'
- if p.checkKeyword("<<fork>>") {
- p.advance()
- state.Type = ast.StateTypeFork
- } else if p.checkKeyword("<<join>>") {
- p.advance()
- state.Type = ast.StateTypeJoin
- } else if p.checkKeyword("<<choice>>") {
- p.advance()
- state.Type = ast.StateTypeChoice
- } else if p.checkKeyword("<<history>>") {
- p.advance()
- state.Type = ast.StateTypeHistory
- } else if p.checkKeyword("<<deepHistory>>") {
- p.advance()
- state.Type = ast.StateTypeDeepHistory
- } else {
- // Parse description
- var descParts []string
- for !p.check(lexer.TokenNewline) && !p.isAtEnd() {
- descParts = append(descParts, p.advance().Value)
- }
- if len(descParts) > 0 {
- desc := strings.TrimSpace(strings.Join(descParts, " "))
- state.Description = &desc
- }
- }
- }
- p.diagram.States[stateName] = state
- return nil
- }
- // parseStateBody parses the contents of a composite state
- func (p *StateParser) parseStateBody(parentState *ast.StateNode) error {
- for !p.check(lexer.TokenCloseBrace) && !p.isAtEnd() {
- if p.check(lexer.TokenNewline) {
- p.advance()
- continue
- }
- // Parse sub-statements (simplified for now)
- if err := p.parseStatement(); err != nil {
- return err
- }
- }
- return nil
- }
- // parseStartEndState parses [*] --> state or state --> [*] transitions
- func (p *StateParser) parseStartEndState() error {
- if !p.check(lexer.TokenOpenBracket) {
- return p.error("expected '['")
- }
- p.advance() // consume '['
- if !p.check(lexer.TokenMult) {
- return p.error("expected '*'")
- }
- p.advance() // consume '*'
- if !p.check(lexer.TokenCloseBracket) {
- return p.error("expected ']'")
- }
- p.advance() // consume ']'
- // Parse arrow
- if !p.checkArrow() {
- return p.error("expected transition arrow")
- }
- p.parseArrow()
- if !p.check(lexer.TokenID) {
- return p.error("expected target state")
- }
- targetState := p.advance().Value
- // Ensure target state exists
- p.ensureState(targetState)
- // Create transition
- transition := &ast.StateTransition{
- From: "[*]",
- To: targetState,
- }
- // Check for 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, " "))
- transition.Label = &label
- }
- }
- p.diagram.Transitions = append(p.diagram.Transitions, transition)
- // Set start state if it's the first [*] transition
- if p.diagram.StartState == nil {
- start := "[*]"
- p.diagram.StartState = &start
- }
- return nil
- }
- // parseStateOrTransition parses either a state definition or transition
- func (p *StateParser) parseStateOrTransition() error {
- stateName := p.advance().Value
- // Ensure state exists
- p.ensureState(stateName)
- // Check for transition arrow
- if p.checkArrow() {
- return p.parseTransition(stateName)
- }
- // Check for colon (description or special type)
- if p.check(lexer.TokenColon) {
- p.advance() // consume ':'
- var descParts []string
- for !p.check(lexer.TokenNewline) && !p.isAtEnd() {
- descParts = append(descParts, p.advance().Value)
- }
- if len(descParts) > 0 {
- desc := strings.TrimSpace(strings.Join(descParts, " "))
- state := p.diagram.States[stateName]
- state.Description = &desc
- }
- }
- return nil
- }
- // parseTransition parses state transitions
- func (p *StateParser) parseTransition(fromState string) error {
- p.parseArrow()
- var toState string
- if p.check(lexer.TokenOpenBracket) {
- // Handle --> [*] end state
- p.advance() // consume '['
- if !p.check(lexer.TokenMult) {
- return p.error("expected '*'")
- }
- p.advance() // consume '*'
- if !p.check(lexer.TokenCloseBracket) {
- return p.error("expected ']'")
- }
- p.advance() // consume ']'
- toState = "[*]"
- // Add to end states if not already there
- found := false
- for _, endState := range p.diagram.EndStates {
- if endState == "[*]" {
- found = true
- break
- }
- }
- if !found {
- p.diagram.EndStates = append(p.diagram.EndStates, "[*]")
- }
- } else if p.check(lexer.TokenID) {
- toState = p.advance().Value
- p.ensureState(toState)
- } else {
- return p.error("expected target state")
- }
- transition := &ast.StateTransition{
- From: fromState,
- To: toState,
- }
- // Check for 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, " "))
- transition.Label = &label
- }
- }
- p.diagram.Transitions = append(p.diagram.Transitions, transition)
- return nil
- }
- // parseArrow parses transition arrows
- func (p *StateParser) parseArrow() string {
- token := p.peek()
- if token.Value == "-->" {
- p.advance()
- return "-->"
- } else if token.Value == "--" && p.checkNext(lexer.TokenCloseAngle) {
- p.advance() // consume '--'
- p.advance() // consume '>'
- return "-->"
- }
- // Default
- p.advance()
- return "-->"
- }
- // parseDirection parses direction statements
- func (p *StateParser) parseDirection() error {
- p.advance() // consume 'direction'
- if !p.check(lexer.TokenID) {
- return p.error("expected direction value")
- }
- direction := p.advance().Value
- p.diagram.Direction = direction
- return nil
- }
- // parseNote parses note statements - placeholder
- func (p *StateParser) parseNote() error {
- return p.skipToNextStatement()
- }
- // ensureState ensures a state exists, creating it if needed
- func (p *StateParser) ensureState(id string) {
- if _, exists := p.diagram.States[id]; !exists {
- state := &ast.StateNode{
- ID: id,
- Label: id,
- Type: ast.StateTypeDefault,
- SubStates: make(map[string]*ast.StateNode),
- CssClasses: make([]string, 0),
- }
- p.diagram.States[id] = state
- }
- }
- // Helper methods
- func (p *StateParser) check(tokenType lexer.TokenType) bool {
- if p.isAtEnd() {
- return false
- }
- return p.peek().Type == tokenType
- }
- func (p *StateParser) checkNext(tokenType lexer.TokenType) bool {
- if p.current+1 >= len(p.tokens) {
- return false
- }
- return p.tokens[p.current+1].Type == tokenType
- }
- func (p *StateParser) 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 *StateParser) checkArrow() bool {
- token := p.peek()
- return token.Value == "-->" || token.Value == "--"
- }
- func (p *StateParser) advance() lexer.Token {
- if !p.isAtEnd() {
- p.current++
- }
- return p.previous()
- }
- func (p *StateParser) isAtEnd() bool {
- return p.current >= len(p.tokens) || p.peek().Type == lexer.TokenEOF
- }
- func (p *StateParser) peek() lexer.Token {
- if p.current >= len(p.tokens) {
- return lexer.Token{Type: lexer.TokenEOF}
- }
- return p.tokens[p.current]
- }
- func (p *StateParser) previous() lexer.Token {
- if p.current <= 0 {
- return lexer.Token{Type: lexer.TokenEOF}
- }
- return p.tokens[p.current-1]
- }
- func (p *StateParser) 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)
- }
- func (p *StateParser) skipToNextStatement() error {
- for !p.isAtEnd() && !p.check(lexer.TokenNewline) {
- p.advance()
- }
- if p.check(lexer.TokenNewline) {
- p.advance()
- }
- return nil
- }
|