state.go 10 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456
  1. // Package parser provides state diagram parsing based on stateDiagram.jison
  2. package parser
  3. import (
  4. "fmt"
  5. "strings"
  6. "mermaid-go/pkg/ast"
  7. "mermaid-go/pkg/lexer"
  8. )
  9. // StateParser implements state diagram parsing following stateDiagram.jison
  10. type StateParser struct {
  11. tokens []lexer.Token
  12. current int
  13. diagram *ast.StateDiagram
  14. }
  15. // NewStateParser creates a new state parser
  16. func NewStateParser() *StateParser {
  17. return &StateParser{
  18. diagram: ast.NewStateDiagram(),
  19. }
  20. }
  21. // Parse parses state diagram syntax
  22. func (p *StateParser) Parse(input string) (*ast.StateDiagram, error) {
  23. // Tokenize
  24. l := lexer.NewLexer(input)
  25. tokens, err := l.Tokenize()
  26. if err != nil {
  27. return nil, fmt.Errorf("lexical analysis failed: %w", err)
  28. }
  29. // Filter tokens
  30. p.tokens = lexer.FilterTokens(tokens)
  31. p.current = 0
  32. p.diagram = ast.NewStateDiagram()
  33. // Parse document
  34. err = p.parseDocument()
  35. if err != nil {
  36. return nil, fmt.Errorf("syntax analysis failed: %w", err)
  37. }
  38. return p.diagram, nil
  39. }
  40. // parseDocument parses the state diagram document
  41. func (p *StateParser) parseDocument() error {
  42. // Expect stateDiagram or stateDiagram-v2
  43. if !p.check(lexer.TokenID) ||
  44. (p.peek().Value != "stateDiagram" && p.peek().Value != "stateDiagram-v2") {
  45. return p.error("expected 'stateDiagram' or 'stateDiagram-v2'")
  46. }
  47. p.advance()
  48. // Parse statements
  49. for !p.isAtEnd() {
  50. if err := p.parseStatement(); err != nil {
  51. return err
  52. }
  53. }
  54. return nil
  55. }
  56. // parseStatement parses individual state diagram statements
  57. func (p *StateParser) parseStatement() error {
  58. if p.isAtEnd() {
  59. return nil
  60. }
  61. switch {
  62. case p.check(lexer.TokenNewline):
  63. p.advance() // Skip newlines
  64. return nil
  65. case p.checkKeyword("direction"):
  66. return p.parseDirection()
  67. case p.checkKeyword("note"):
  68. return p.parseNote()
  69. case p.checkKeyword("state"):
  70. return p.parseState()
  71. case p.check(lexer.TokenOpenBracket):
  72. // Handle [*] start/end states
  73. return p.parseStartEndState()
  74. case p.check(lexer.TokenID):
  75. // Try to parse as state or transition
  76. return p.parseStateOrTransition()
  77. default:
  78. token := p.peek()
  79. return p.error(fmt.Sprintf("unexpected token: %s", token.Value))
  80. }
  81. }
  82. // parseState parses state declarations
  83. func (p *StateParser) parseState() error {
  84. p.advance() // consume 'state'
  85. if !p.check(lexer.TokenID) {
  86. return p.error("expected state name")
  87. }
  88. stateName := p.advance().Value
  89. state := &ast.StateNode{
  90. ID: stateName,
  91. Label: stateName,
  92. Type: ast.StateTypeDefault,
  93. SubStates: make(map[string]*ast.StateNode),
  94. CssClasses: make([]string, 0),
  95. }
  96. // Check for 'as' alias
  97. if p.checkKeyword("as") {
  98. p.advance() // consume 'as'
  99. if !p.check(lexer.TokenID) && !p.check(lexer.TokenString) {
  100. return p.error("expected state label after 'as'")
  101. }
  102. label := p.advance().Value
  103. if strings.HasPrefix(label, "\"") && strings.HasSuffix(label, "\"") {
  104. label = label[1 : len(label)-1] // Remove quotes
  105. }
  106. state.Label = label
  107. }
  108. // Check for state body (composite state)
  109. if p.check(lexer.TokenOpenBrace) {
  110. p.advance() // consume '{'
  111. err := p.parseStateBody(state)
  112. if err != nil {
  113. return err
  114. }
  115. if !p.check(lexer.TokenCloseBrace) {
  116. return p.error("expected '}'")
  117. }
  118. p.advance() // consume '}'
  119. }
  120. // Check for special state types
  121. if p.check(lexer.TokenColon) {
  122. p.advance() // consume ':'
  123. if p.checkKeyword("<<fork>>") {
  124. p.advance()
  125. state.Type = ast.StateTypeFork
  126. } else if p.checkKeyword("<<join>>") {
  127. p.advance()
  128. state.Type = ast.StateTypeJoin
  129. } else if p.checkKeyword("<<choice>>") {
  130. p.advance()
  131. state.Type = ast.StateTypeChoice
  132. } else if p.checkKeyword("<<history>>") {
  133. p.advance()
  134. state.Type = ast.StateTypeHistory
  135. } else if p.checkKeyword("<<deepHistory>>") {
  136. p.advance()
  137. state.Type = ast.StateTypeDeepHistory
  138. } else {
  139. // Parse description
  140. var descParts []string
  141. for !p.check(lexer.TokenNewline) && !p.isAtEnd() {
  142. descParts = append(descParts, p.advance().Value)
  143. }
  144. if len(descParts) > 0 {
  145. desc := strings.TrimSpace(strings.Join(descParts, " "))
  146. state.Description = &desc
  147. }
  148. }
  149. }
  150. p.diagram.States[stateName] = state
  151. return nil
  152. }
  153. // parseStateBody parses the contents of a composite state
  154. func (p *StateParser) parseStateBody(parentState *ast.StateNode) error {
  155. for !p.check(lexer.TokenCloseBrace) && !p.isAtEnd() {
  156. if p.check(lexer.TokenNewline) {
  157. p.advance()
  158. continue
  159. }
  160. // Parse sub-statements (simplified for now)
  161. if err := p.parseStatement(); err != nil {
  162. return err
  163. }
  164. }
  165. return nil
  166. }
  167. // parseStartEndState parses [*] --> state or state --> [*] transitions
  168. func (p *StateParser) parseStartEndState() error {
  169. if !p.check(lexer.TokenOpenBracket) {
  170. return p.error("expected '['")
  171. }
  172. p.advance() // consume '['
  173. if !p.check(lexer.TokenMult) {
  174. return p.error("expected '*'")
  175. }
  176. p.advance() // consume '*'
  177. if !p.check(lexer.TokenCloseBracket) {
  178. return p.error("expected ']'")
  179. }
  180. p.advance() // consume ']'
  181. // Parse arrow
  182. if !p.checkArrow() {
  183. return p.error("expected transition arrow")
  184. }
  185. p.parseArrow()
  186. if !p.check(lexer.TokenID) {
  187. return p.error("expected target state")
  188. }
  189. targetState := p.advance().Value
  190. // Ensure target state exists
  191. p.ensureState(targetState)
  192. // Create transition
  193. transition := &ast.StateTransition{
  194. From: "[*]",
  195. To: targetState,
  196. }
  197. // Check for label
  198. if p.check(lexer.TokenColon) {
  199. p.advance() // consume ':'
  200. var labelParts []string
  201. for !p.check(lexer.TokenNewline) && !p.isAtEnd() {
  202. labelParts = append(labelParts, p.advance().Value)
  203. }
  204. if len(labelParts) > 0 {
  205. label := strings.TrimSpace(strings.Join(labelParts, " "))
  206. transition.Label = &label
  207. }
  208. }
  209. p.diagram.Transitions = append(p.diagram.Transitions, transition)
  210. // Set start state if it's the first [*] transition
  211. if p.diagram.StartState == nil {
  212. start := "[*]"
  213. p.diagram.StartState = &start
  214. }
  215. return nil
  216. }
  217. // parseStateOrTransition parses either a state definition or transition
  218. func (p *StateParser) parseStateOrTransition() error {
  219. stateName := p.advance().Value
  220. // Ensure state exists
  221. p.ensureState(stateName)
  222. // Check for transition arrow
  223. if p.checkArrow() {
  224. return p.parseTransition(stateName)
  225. }
  226. // Check for colon (description or special type)
  227. if p.check(lexer.TokenColon) {
  228. p.advance() // consume ':'
  229. var descParts []string
  230. for !p.check(lexer.TokenNewline) && !p.isAtEnd() {
  231. descParts = append(descParts, p.advance().Value)
  232. }
  233. if len(descParts) > 0 {
  234. desc := strings.TrimSpace(strings.Join(descParts, " "))
  235. state := p.diagram.States[stateName]
  236. state.Description = &desc
  237. }
  238. }
  239. return nil
  240. }
  241. // parseTransition parses state transitions
  242. func (p *StateParser) parseTransition(fromState string) error {
  243. p.parseArrow()
  244. var toState string
  245. if p.check(lexer.TokenOpenBracket) {
  246. // Handle --> [*] end state
  247. p.advance() // consume '['
  248. if !p.check(lexer.TokenMult) {
  249. return p.error("expected '*'")
  250. }
  251. p.advance() // consume '*'
  252. if !p.check(lexer.TokenCloseBracket) {
  253. return p.error("expected ']'")
  254. }
  255. p.advance() // consume ']'
  256. toState = "[*]"
  257. // Add to end states if not already there
  258. found := false
  259. for _, endState := range p.diagram.EndStates {
  260. if endState == "[*]" {
  261. found = true
  262. break
  263. }
  264. }
  265. if !found {
  266. p.diagram.EndStates = append(p.diagram.EndStates, "[*]")
  267. }
  268. } else if p.check(lexer.TokenID) {
  269. toState = p.advance().Value
  270. p.ensureState(toState)
  271. } else {
  272. return p.error("expected target state")
  273. }
  274. transition := &ast.StateTransition{
  275. From: fromState,
  276. To: toState,
  277. }
  278. // Check for label
  279. if p.check(lexer.TokenColon) {
  280. p.advance() // consume ':'
  281. var labelParts []string
  282. for !p.check(lexer.TokenNewline) && !p.isAtEnd() {
  283. labelParts = append(labelParts, p.advance().Value)
  284. }
  285. if len(labelParts) > 0 {
  286. label := strings.TrimSpace(strings.Join(labelParts, " "))
  287. transition.Label = &label
  288. }
  289. }
  290. p.diagram.Transitions = append(p.diagram.Transitions, transition)
  291. return nil
  292. }
  293. // parseArrow parses transition arrows
  294. func (p *StateParser) parseArrow() string {
  295. token := p.peek()
  296. if token.Value == "-->" {
  297. p.advance()
  298. return "-->"
  299. } else if token.Value == "--" && p.checkNext(lexer.TokenCloseAngle) {
  300. p.advance() // consume '--'
  301. p.advance() // consume '>'
  302. return "-->"
  303. }
  304. // Default
  305. p.advance()
  306. return "-->"
  307. }
  308. // parseDirection parses direction statements
  309. func (p *StateParser) parseDirection() error {
  310. p.advance() // consume 'direction'
  311. if !p.check(lexer.TokenID) {
  312. return p.error("expected direction value")
  313. }
  314. direction := p.advance().Value
  315. p.diagram.Direction = direction
  316. return nil
  317. }
  318. // parseNote parses note statements - placeholder
  319. func (p *StateParser) parseNote() error {
  320. return p.skipToNextStatement()
  321. }
  322. // ensureState ensures a state exists, creating it if needed
  323. func (p *StateParser) ensureState(id string) {
  324. if _, exists := p.diagram.States[id]; !exists {
  325. state := &ast.StateNode{
  326. ID: id,
  327. Label: id,
  328. Type: ast.StateTypeDefault,
  329. SubStates: make(map[string]*ast.StateNode),
  330. CssClasses: make([]string, 0),
  331. }
  332. p.diagram.States[id] = state
  333. }
  334. }
  335. // Helper methods
  336. func (p *StateParser) check(tokenType lexer.TokenType) bool {
  337. if p.isAtEnd() {
  338. return false
  339. }
  340. return p.peek().Type == tokenType
  341. }
  342. func (p *StateParser) checkNext(tokenType lexer.TokenType) bool {
  343. if p.current+1 >= len(p.tokens) {
  344. return false
  345. }
  346. return p.tokens[p.current+1].Type == tokenType
  347. }
  348. func (p *StateParser) checkKeyword(keyword string) bool {
  349. if p.isAtEnd() {
  350. return false
  351. }
  352. token := p.peek()
  353. return token.Type == lexer.TokenID && strings.ToLower(token.Value) == strings.ToLower(keyword)
  354. }
  355. func (p *StateParser) checkArrow() bool {
  356. token := p.peek()
  357. return token.Value == "-->" || token.Value == "--"
  358. }
  359. func (p *StateParser) advance() lexer.Token {
  360. if !p.isAtEnd() {
  361. p.current++
  362. }
  363. return p.previous()
  364. }
  365. func (p *StateParser) isAtEnd() bool {
  366. return p.current >= len(p.tokens) || p.peek().Type == lexer.TokenEOF
  367. }
  368. func (p *StateParser) peek() lexer.Token {
  369. if p.current >= len(p.tokens) {
  370. return lexer.Token{Type: lexer.TokenEOF}
  371. }
  372. return p.tokens[p.current]
  373. }
  374. func (p *StateParser) previous() lexer.Token {
  375. if p.current <= 0 {
  376. return lexer.Token{Type: lexer.TokenEOF}
  377. }
  378. return p.tokens[p.current-1]
  379. }
  380. func (p *StateParser) error(message string) error {
  381. token := p.peek()
  382. return fmt.Errorf("parse error at line %d, column %d: %s (got %s)",
  383. token.Line, token.Column, message, token.Type)
  384. }
  385. func (p *StateParser) skipToNextStatement() error {
  386. for !p.isAtEnd() && !p.check(lexer.TokenNewline) {
  387. p.advance()
  388. }
  389. if p.check(lexer.TokenNewline) {
  390. p.advance()
  391. }
  392. return nil
  393. }