state.go 25 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973974975976977
  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. stateMap map[string]*ast.StateNode // Keep track of states by name for quick lookup
  15. }
  16. // NewStateParser creates a new state parser
  17. func NewStateParser() *StateParser {
  18. return &StateParser{
  19. diagram: ast.NewStateDiagram(),
  20. stateMap: make(map[string]*ast.StateNode),
  21. }
  22. }
  23. // Parse parses state diagram syntax
  24. func (p *StateParser) Parse(input string) (*ast.StateDiagram, error) {
  25. // Tokenize
  26. l := lexer.NewLexer(input)
  27. tokens, err := l.Tokenize()
  28. if err != nil {
  29. return nil, fmt.Errorf("lexical analysis failed: %w", err)
  30. }
  31. // Filter tokens
  32. p.tokens = lexer.FilterTokens(tokens)
  33. p.current = 0
  34. p.diagram = ast.NewStateDiagram()
  35. p.stateMap = make(map[string]*ast.StateNode)
  36. // Parse document
  37. err = p.parseDocument()
  38. if err != nil {
  39. return nil, fmt.Errorf("syntax analysis failed: %w", err)
  40. }
  41. return p.diagram, nil
  42. }
  43. // parseDocument parses the state diagram document
  44. func (p *StateParser) parseDocument() error {
  45. // Expect stateDiagram or stateDiagram-v2
  46. if !p.check(lexer.TokenID) ||
  47. (p.peek().Value != "stateDiagram" && p.peek().Value != "stateDiagram-v2") {
  48. return p.error("expected 'stateDiagram' or 'stateDiagram-v2'")
  49. }
  50. p.advance()
  51. // Parse statements
  52. for !p.isAtEnd() {
  53. if err := p.parseStatement(); err != nil {
  54. return err
  55. }
  56. }
  57. return nil
  58. }
  59. // parseStatement parses individual state diagram statements
  60. func (p *StateParser) parseStatement() error {
  61. if p.isAtEnd() {
  62. return nil
  63. }
  64. switch {
  65. case p.check(lexer.TokenNewline):
  66. p.advance() // Skip newlines
  67. return nil
  68. case p.checkKeyword("direction"):
  69. return p.parseDirection()
  70. case p.checkKeyword("note"):
  71. return p.parseNote()
  72. case p.checkKeyword("state"):
  73. return p.parseState()
  74. case p.check(lexer.TokenEntry):
  75. return p.parseStateAction("entry")
  76. case p.check(lexer.TokenExit):
  77. return p.parseStateAction("exit")
  78. case p.check(lexer.TokenDo):
  79. return p.parseStateAction("do")
  80. case p.check(lexer.TokenOpenBracket):
  81. // Handle [*] start/end states
  82. return p.parseStartEndState()
  83. case p.check(lexer.TokenID):
  84. // Try to parse as state or transition
  85. return p.parseStateOrTransition()
  86. default:
  87. token := p.peek()
  88. return p.error(fmt.Sprintf("unexpected token: %s", token.Value))
  89. }
  90. }
  91. // parseState parses state declarations
  92. func (p *StateParser) parseState() error {
  93. p.advance() // consume 'state'
  94. if !p.check(lexer.TokenID) {
  95. return p.error("expected state name")
  96. }
  97. stateName := p.advance().Value
  98. // Check if state already exists
  99. var state *ast.StateNode
  100. if existingState, exists := p.stateMap[stateName]; exists {
  101. state = existingState
  102. } else {
  103. state = &ast.StateNode{
  104. ID: stateName,
  105. Label: stateName,
  106. Type: ast.StateTypeDefault,
  107. SubStates: make(map[string]*ast.StateNode),
  108. CssClasses: make([]string, 0),
  109. }
  110. }
  111. // Check for 'as' alias
  112. if p.checkKeyword("as") {
  113. p.advance() // consume 'as'
  114. if !p.check(lexer.TokenID) && !p.check(lexer.TokenString) {
  115. return p.error("expected state label after 'as'")
  116. }
  117. label := p.advance().Value
  118. if strings.HasPrefix(label, "\"") && strings.HasSuffix(label, "\"") {
  119. label = label[1 : len(label)-1] // Remove quotes
  120. }
  121. state.Label = label
  122. }
  123. // Check for state body (composite state)
  124. if p.check(lexer.TokenOpenBrace) {
  125. p.advance() // consume '{'
  126. err := p.parseStateBody(state)
  127. if err != nil {
  128. return err
  129. }
  130. if !p.check(lexer.TokenCloseBrace) {
  131. return p.error("expected '}'")
  132. }
  133. p.advance() // consume '}'
  134. }
  135. // Check for special state types
  136. if p.check(lexer.TokenColon) {
  137. p.advance() // consume ':'
  138. if p.checkKeyword("<<fork>>") {
  139. p.advance()
  140. state.Type = ast.StateTypeFork
  141. } else if p.checkKeyword("<<join>>") {
  142. p.advance()
  143. state.Type = ast.StateTypeJoin
  144. } else if p.checkKeyword("<<choice>>") {
  145. p.advance()
  146. state.Type = ast.StateTypeChoice
  147. } else if p.checkKeyword("<<history>>") {
  148. p.advance()
  149. state.Type = ast.StateTypeHistory
  150. } else if p.checkKeyword("<<deepHistory>>") {
  151. p.advance()
  152. state.Type = ast.StateTypeDeepHistory
  153. } else {
  154. // Parse description
  155. var descParts []string
  156. for !p.check(lexer.TokenNewline) && !p.isAtEnd() {
  157. descParts = append(descParts, p.advance().Value)
  158. }
  159. if len(descParts) > 0 {
  160. desc := strings.TrimSpace(strings.Join(descParts, " "))
  161. state.Description = &desc
  162. }
  163. }
  164. }
  165. // Only add state if it doesn't already exist
  166. if _, exists := p.stateMap[stateName]; !exists {
  167. p.stateMap[stateName] = state
  168. p.diagram.States = append(p.diagram.States, state)
  169. }
  170. return nil
  171. }
  172. // parseStateBody parses the contents of a composite state
  173. func (p *StateParser) parseStateBody(parentState *ast.StateNode) error {
  174. for !p.check(lexer.TokenCloseBrace) && !p.isAtEnd() {
  175. if p.check(lexer.TokenNewline) {
  176. p.advance()
  177. continue
  178. }
  179. // Parse substates and transitions within the composite state
  180. if p.checkKeyword("state") {
  181. // Check if this is a nested composite state
  182. // Look ahead: state ID { means nested composite state
  183. if p.peekNext().Type == lexer.TokenID && p.current+2 < len(p.tokens) && p.tokens[p.current+2].Type == lexer.TokenOpenBrace {
  184. // This is a nested composite state, parse it as a full state
  185. if err := p.parseNestedCompositeState(parentState); err != nil {
  186. return err
  187. }
  188. } else {
  189. // This is a simple substate
  190. if err := p.parseSubState(parentState); err != nil {
  191. return err
  192. }
  193. }
  194. } else if p.check(lexer.TokenOpenBracket) {
  195. // Handle [*] start/end states within composite state
  196. if err := p.parseStartEndStateInComposite(parentState); err != nil {
  197. return err
  198. }
  199. } else if p.check(lexer.TokenID) {
  200. // In composite state, ID followed by --> is a transition
  201. // We need to parse the transition and ensure the states exist as substates
  202. if err := p.parseStateOrTransitionInComposite(parentState); err != nil {
  203. return err
  204. }
  205. } else {
  206. token := p.peek()
  207. return p.error(fmt.Sprintf("unexpected token in composite state: %s", token.Value))
  208. }
  209. }
  210. return nil
  211. }
  212. // parseSubState parses a substate within a composite state
  213. func (p *StateParser) parseSubState(parentState *ast.StateNode) error {
  214. if !p.checkKeyword("state") {
  215. return p.error("expected 'state'")
  216. }
  217. p.advance() // consume 'state'
  218. if !p.check(lexer.TokenID) {
  219. return p.error("expected state name")
  220. }
  221. stateName := p.advance().Value
  222. // Create substate
  223. subState := &ast.StateNode{
  224. ID: stateName,
  225. Label: stateName,
  226. Type: ast.StateTypeDefault,
  227. SubStates: make(map[string]*ast.StateNode),
  228. CssClasses: make([]string, 0),
  229. }
  230. // Add alias if different from ID
  231. if p.checkKeyword("as") {
  232. p.advance() // consume 'as'
  233. if !p.check(lexer.TokenID) && !p.check(lexer.TokenString) {
  234. return p.error("expected alias name")
  235. }
  236. alias := p.advance().Value
  237. // Remove quotes if present
  238. if len(alias) > 2 && alias[0] == '"' && alias[len(alias)-1] == '"' {
  239. alias = alias[1 : len(alias)-1]
  240. }
  241. subState.Label = alias
  242. }
  243. // Check for description or special type
  244. if p.check(lexer.TokenColon) {
  245. p.advance() // consume ':'
  246. if p.checkKeyword("<<fork>>") {
  247. p.advance()
  248. subState.Type = ast.StateTypeFork
  249. } else if p.checkKeyword("<<join>>") {
  250. p.advance()
  251. subState.Type = ast.StateTypeJoin
  252. } else if p.checkKeyword("<<choice>>") {
  253. p.advance()
  254. subState.Type = ast.StateTypeChoice
  255. } else if p.checkKeyword("<<history>>") {
  256. p.advance()
  257. subState.Type = ast.StateTypeHistory
  258. } else if p.checkKeyword("<<deepHistory>>") {
  259. p.advance()
  260. subState.Type = ast.StateTypeDeepHistory
  261. } else {
  262. // Parse description
  263. var descParts []string
  264. for !p.check(lexer.TokenNewline) && !p.isAtEnd() {
  265. descParts = append(descParts, p.advance().Value)
  266. }
  267. if len(descParts) > 0 {
  268. desc := strings.TrimSpace(strings.Join(descParts, " "))
  269. subState.Description = &desc
  270. }
  271. }
  272. }
  273. // Add to parent state's substates
  274. parentState.SubStates[subState.ID] = subState
  275. return nil
  276. }
  277. // parseNestedCompositeState parses a nested composite state within a parent composite state
  278. func (p *StateParser) parseNestedCompositeState(parentState *ast.StateNode) error {
  279. if !p.checkKeyword("state") {
  280. return p.error("expected 'state'")
  281. }
  282. p.advance() // consume 'state'
  283. if !p.check(lexer.TokenID) {
  284. return p.error("expected state name")
  285. }
  286. stateName := p.advance().Value
  287. // Create nested composite state
  288. nestedState := &ast.StateNode{
  289. ID: stateName,
  290. Label: stateName,
  291. Type: ast.StateTypeDefault,
  292. SubStates: make(map[string]*ast.StateNode),
  293. CssClasses: make([]string, 0),
  294. }
  295. // Check for 'as' alias
  296. if p.checkKeyword("as") {
  297. p.advance() // consume 'as'
  298. if !p.check(lexer.TokenID) && !p.check(lexer.TokenString) {
  299. return p.error("expected alias name")
  300. }
  301. alias := p.advance().Value
  302. // Remove quotes if present
  303. if len(alias) > 2 && alias[0] == '"' && alias[len(alias)-1] == '"' {
  304. alias = alias[1 : len(alias)-1]
  305. }
  306. nestedState.Label = alias
  307. }
  308. // Parse the nested composite state body
  309. if p.check(lexer.TokenOpenBrace) {
  310. p.advance() // consume '{'
  311. err := p.parseStateBody(nestedState)
  312. if err != nil {
  313. return err
  314. }
  315. if !p.check(lexer.TokenCloseBrace) {
  316. return p.error("expected '}'")
  317. }
  318. p.advance() // consume '}'
  319. }
  320. // Add to parent state's substates
  321. parentState.SubStates[nestedState.ID] = nestedState
  322. return nil
  323. }
  324. // parseStateOrTransitionInComposite parses a state or transition within a composite state
  325. func (p *StateParser) parseStateOrTransitionInComposite(parentState *ast.StateNode) error {
  326. // This should be a transition within the composite state
  327. // Parse the transition normally, but ensure states are added as substates
  328. return p.parseStateOrTransitionWithParent(parentState)
  329. }
  330. // parseStateOrTransitionWithParent parses a state or transition with a parent composite state
  331. func (p *StateParser) parseStateOrTransitionWithParent(parentState *ast.StateNode) error {
  332. stateName := p.advance().Value
  333. // Ensure state exists as substate in parent
  334. p.ensureStateAsSubstate(parentState, stateName)
  335. // Check for transition arrow
  336. if p.checkArrow() {
  337. return p.parseTransitionWithParent(parentState, stateName)
  338. }
  339. // For now, just handle transitions in composite states
  340. // Other cases (state actions, descriptions) can be handled later
  341. return nil
  342. }
  343. // ensureStateAsSubstate ensures a state exists as a substate in the parent
  344. func (p *StateParser) ensureStateAsSubstate(parentState *ast.StateNode, id string) {
  345. if _, exists := parentState.SubStates[id]; !exists {
  346. subState := &ast.StateNode{
  347. ID: id,
  348. Label: id,
  349. Type: ast.StateTypeDefault,
  350. SubStates: make(map[string]*ast.StateNode),
  351. CssClasses: make([]string, 0),
  352. }
  353. parentState.SubStates[id] = subState
  354. }
  355. }
  356. // parseTransitionWithParent parses a transition within a composite state
  357. func (p *StateParser) parseTransitionWithParent(parentState *ast.StateNode, fromState string) error {
  358. if !p.checkArrow() {
  359. return p.error("expected transition arrow")
  360. }
  361. p.advance() // consume arrow
  362. if p.isAtEnd() {
  363. return p.error("unexpected end of input")
  364. }
  365. var toState string
  366. if p.check(lexer.TokenOpenBracket) {
  367. // Handle [*] end state
  368. p.advance() // consume '['
  369. if !p.check(lexer.TokenMult) {
  370. return p.error("expected '*' in [*]")
  371. }
  372. p.advance() // consume '*'
  373. if !p.check(lexer.TokenCloseBracket) {
  374. return p.error("expected ']' in [*]")
  375. }
  376. p.advance() // consume ']'
  377. toState = "[*]"
  378. } else if p.check(lexer.TokenID) {
  379. toState = p.advance().Value
  380. // Ensure toState exists as substate
  381. p.ensureStateAsSubstate(parentState, toState)
  382. } else {
  383. return p.error("expected state name or [*]")
  384. }
  385. // Create transition
  386. transition := &ast.StateTransition{
  387. From: fromState,
  388. To: toState,
  389. }
  390. // Parse transition decorations if present
  391. if p.check(lexer.TokenColon) {
  392. p.advance() // consume ':'
  393. var labelParts []string
  394. for !p.check(lexer.TokenNewline) && !p.isAtEnd() {
  395. labelParts = append(labelParts, p.advance().Value)
  396. }
  397. if len(labelParts) > 0 {
  398. label := strings.TrimSpace(strings.Join(labelParts, " "))
  399. transition.Label = &label
  400. }
  401. }
  402. // Add transition to diagram
  403. p.diagram.Transitions = append(p.diagram.Transitions, transition)
  404. return nil
  405. }
  406. // parseStartEndStateInComposite parses [*] transitions within a composite state
  407. func (p *StateParser) parseStartEndStateInComposite(parentState *ast.StateNode) error {
  408. if !p.check(lexer.TokenOpenBracket) {
  409. return p.error("expected '['")
  410. }
  411. p.advance() // consume '['
  412. if !p.check(lexer.TokenMult) {
  413. return p.error("expected '*'")
  414. }
  415. p.advance() // consume '*'
  416. if !p.check(lexer.TokenCloseBracket) {
  417. return p.error("expected ']'")
  418. }
  419. p.advance() // consume ']'
  420. // Expect arrow
  421. if !p.checkArrow() {
  422. return p.error("expected transition arrow")
  423. }
  424. p.advance() // consume arrow
  425. // Parse target state
  426. if p.isAtEnd() {
  427. return p.error("unexpected end of input")
  428. }
  429. var toState string
  430. if p.check(lexer.TokenOpenBracket) {
  431. // Handle [*] end state
  432. p.advance() // consume '['
  433. if !p.check(lexer.TokenMult) {
  434. return p.error("expected '*' in [*]")
  435. }
  436. p.advance() // consume '*'
  437. if !p.check(lexer.TokenCloseBracket) {
  438. return p.error("expected ']' in [*]")
  439. }
  440. p.advance() // consume ']'
  441. toState = "[*]"
  442. } else if p.check(lexer.TokenID) {
  443. toState = p.advance().Value
  444. // Ensure toState exists as substate
  445. p.ensureStateAsSubstate(parentState, toState)
  446. } else {
  447. return p.error("expected state name or [*]")
  448. }
  449. // Create transition
  450. transition := &ast.StateTransition{
  451. From: "[*]",
  452. To: toState,
  453. }
  454. // Parse transition decorations if present
  455. if p.check(lexer.TokenColon) {
  456. p.advance() // consume ':'
  457. var labelParts []string
  458. for !p.check(lexer.TokenNewline) && !p.isAtEnd() {
  459. labelParts = append(labelParts, p.advance().Value)
  460. }
  461. if len(labelParts) > 0 {
  462. label := strings.TrimSpace(strings.Join(labelParts, " "))
  463. transition.Label = &label
  464. }
  465. }
  466. // Add transition to diagram
  467. p.diagram.Transitions = append(p.diagram.Transitions, transition)
  468. return nil
  469. }
  470. // parseStartEndState parses [*] --> state or state --> [*] transitions
  471. func (p *StateParser) parseStartEndState() error {
  472. if !p.check(lexer.TokenOpenBracket) {
  473. return p.error("expected '['")
  474. }
  475. p.advance() // consume '['
  476. if !p.check(lexer.TokenMult) {
  477. return p.error("expected '*'")
  478. }
  479. p.advance() // consume '*'
  480. if !p.check(lexer.TokenCloseBracket) {
  481. return p.error("expected ']'")
  482. }
  483. p.advance() // consume ']'
  484. // Parse arrow
  485. if !p.checkArrow() {
  486. return p.error("expected transition arrow")
  487. }
  488. p.parseArrow()
  489. if !p.check(lexer.TokenID) {
  490. return p.error("expected target state")
  491. }
  492. targetState := p.advance().Value
  493. // Ensure target state exists
  494. p.ensureState(targetState)
  495. // Create transition
  496. transition := &ast.StateTransition{
  497. From: "[*]",
  498. To: targetState,
  499. }
  500. // Check for label
  501. if p.check(lexer.TokenColon) {
  502. p.advance() // consume ':'
  503. var labelParts []string
  504. for !p.check(lexer.TokenNewline) && !p.isAtEnd() {
  505. labelParts = append(labelParts, p.advance().Value)
  506. }
  507. if len(labelParts) > 0 {
  508. label := strings.TrimSpace(strings.Join(labelParts, " "))
  509. transition.Label = &label
  510. }
  511. }
  512. p.diagram.Transitions = append(p.diagram.Transitions, transition)
  513. // Set start state if it's the first [*] transition
  514. if p.diagram.StartState == nil {
  515. start := "[*]"
  516. p.diagram.StartState = &start
  517. }
  518. return nil
  519. }
  520. // parseStateOrTransition parses either a state definition or transition
  521. func (p *StateParser) parseStateOrTransition() error {
  522. stateName := p.advance().Value
  523. // Ensure state exists
  524. p.ensureState(stateName)
  525. // Check for transition arrow
  526. if p.checkArrow() {
  527. return p.parseTransition(stateName)
  528. }
  529. // Check for colon (description, special type, or state action)
  530. if p.check(lexer.TokenColon) {
  531. p.advance() // consume ':'
  532. // Check if this is a state action (entry/exit/do)
  533. if p.check(lexer.TokenEntry) || p.check(lexer.TokenExit) || p.check(lexer.TokenDo) {
  534. var actionType string
  535. if p.check(lexer.TokenEntry) {
  536. actionType = "entry"
  537. } else if p.check(lexer.TokenExit) {
  538. actionType = "exit"
  539. } else if p.check(lexer.TokenDo) {
  540. actionType = "do"
  541. }
  542. p.advance() // consume action type
  543. // Parse action content (everything until newline)
  544. var actionParts []string
  545. for !p.check(lexer.TokenNewline) && !p.isAtEnd() {
  546. actionParts = append(actionParts, p.advance().Value)
  547. }
  548. actionContent := strings.TrimSpace(strings.Join(actionParts, " "))
  549. state := p.stateMap[stateName]
  550. switch actionType {
  551. case "entry":
  552. state.EntryAction = &actionContent
  553. case "exit":
  554. state.ExitAction = &actionContent
  555. case "do":
  556. state.DoAction = &actionContent
  557. }
  558. } else {
  559. // Regular description or special type
  560. var descParts []string
  561. for !p.check(lexer.TokenNewline) && !p.isAtEnd() {
  562. descParts = append(descParts, p.advance().Value)
  563. }
  564. if len(descParts) > 0 {
  565. desc := strings.TrimSpace(strings.Join(descParts, " "))
  566. state := p.stateMap[stateName]
  567. // Check if this is a special state type
  568. switch desc {
  569. case "<<fork>>":
  570. state.Type = ast.StateTypeFork
  571. case "<<join>>":
  572. state.Type = ast.StateTypeJoin
  573. case "<<choice>>":
  574. state.Type = ast.StateTypeChoice
  575. case "<<history>>":
  576. state.Type = ast.StateTypeHistory
  577. case "<<deepHistory>>":
  578. state.Type = ast.StateTypeDeepHistory
  579. default:
  580. state.Description = &desc
  581. }
  582. }
  583. }
  584. }
  585. return nil
  586. }
  587. // parseTransition parses state transitions
  588. func (p *StateParser) parseTransition(fromState string) error {
  589. p.parseArrow()
  590. var toState string
  591. if p.check(lexer.TokenOpenBracket) {
  592. // Handle --> [*] end state
  593. p.advance() // consume '['
  594. if !p.check(lexer.TokenMult) {
  595. return p.error("expected '*'")
  596. }
  597. p.advance() // consume '*'
  598. if !p.check(lexer.TokenCloseBracket) {
  599. return p.error("expected ']'")
  600. }
  601. p.advance() // consume ']'
  602. toState = "[*]"
  603. // Add to end states if not already there
  604. found := false
  605. for _, endState := range p.diagram.EndStates {
  606. if endState == "[*]" {
  607. found = true
  608. break
  609. }
  610. }
  611. if !found {
  612. p.diagram.EndStates = append(p.diagram.EndStates, "[*]")
  613. }
  614. } else if p.check(lexer.TokenID) {
  615. toState = p.advance().Value
  616. p.ensureState(toState)
  617. } else {
  618. return p.error("expected target state")
  619. }
  620. transition := &ast.StateTransition{
  621. From: fromState,
  622. To: toState,
  623. }
  624. // Optional decorations: ':' label, '[guard]' and '/action' in any order after ':'
  625. if p.check(lexer.TokenColon) {
  626. p.advance() // consume ':'
  627. // Collect rest of the line
  628. var parts []string
  629. for !p.check(lexer.TokenNewline) && !p.isAtEnd() {
  630. parts = append(parts, p.advance().Value)
  631. }
  632. raw := strings.TrimSpace(strings.Join(parts, " "))
  633. // Extract [guard]
  634. guardStart := strings.Index(raw, "[")
  635. guardEnd := strings.Index(raw, "]")
  636. if guardStart >= 0 && guardEnd > guardStart {
  637. cond := strings.TrimSpace(raw[guardStart+1 : guardEnd])
  638. if cond != "" {
  639. transition.Condition = &cond
  640. }
  641. // Remove guard from raw
  642. raw = strings.TrimSpace(raw[:guardStart] + raw[guardEnd+1:])
  643. }
  644. // Extract '/action'
  645. if slash := strings.Index(raw, "/"); slash >= 0 {
  646. action := strings.TrimSpace(raw[slash+1:])
  647. if action != "" {
  648. transition.Action = &action
  649. }
  650. raw = strings.TrimSpace(raw[:slash])
  651. }
  652. if raw != "" {
  653. lbl := strings.TrimSpace(raw)
  654. transition.Label = &lbl
  655. }
  656. }
  657. p.diagram.Transitions = append(p.diagram.Transitions, transition)
  658. return nil
  659. }
  660. // parseArrow parses transition arrows
  661. func (p *StateParser) parseArrow() string {
  662. token := p.peek()
  663. if token.Value == "-->" {
  664. p.advance()
  665. return "-->"
  666. } else if token.Value == "--" && p.checkNext(lexer.TokenCloseAngle) {
  667. p.advance() // consume '--'
  668. p.advance() // consume '>'
  669. return "-->"
  670. }
  671. // Default
  672. p.advance()
  673. return "-->"
  674. }
  675. // parseDirection parses direction statements
  676. func (p *StateParser) parseDirection() error {
  677. p.advance() // consume 'direction'
  678. if !p.check(lexer.TokenID) {
  679. return p.error("expected direction value")
  680. }
  681. direction := p.advance().Value
  682. p.diagram.Direction = direction
  683. return nil
  684. }
  685. // parseNote parses note statements - placeholder
  686. func (p *StateParser) parseNote() error {
  687. p.advance() // consume 'note'
  688. // note left of <state> : text
  689. // note right of <state> : text
  690. // note over <state> : text (treat as over)
  691. var place ast.NotePlace
  692. if p.checkKeyword("left") {
  693. p.advance()
  694. if !p.checkKeyword("of") {
  695. return p.error("expected 'of' after 'left'")
  696. }
  697. p.advance()
  698. place = ast.NotePlaceLeft
  699. } else if p.checkKeyword("right") {
  700. p.advance()
  701. if !p.checkKeyword("of") {
  702. return p.error("expected 'of' after 'right'")
  703. }
  704. p.advance()
  705. place = ast.NotePlaceRight
  706. } else if p.checkKeyword("over") {
  707. p.advance()
  708. place = ast.NotePlaceOver
  709. } else {
  710. return p.error("expected note placement (left of, right of, over)")
  711. }
  712. if !p.check(lexer.TokenID) {
  713. return p.error("expected state ID for note")
  714. }
  715. stateID := p.advance().Value
  716. if !p.check(lexer.TokenColon) {
  717. return p.error("expected ':' after state in note")
  718. }
  719. p.advance()
  720. // Collect text
  721. var txt []string
  722. for !p.check(lexer.TokenNewline) && !p.isAtEnd() {
  723. txt = append(txt, p.advance().Value)
  724. }
  725. noteText := strings.TrimSpace(strings.Join(txt, " "))
  726. // Attach to state; ensure exists
  727. p.ensureState(stateID)
  728. if st, ok := p.stateMap[stateID]; ok {
  729. st.Note = &ast.StateNote{Position: place, Text: noteText}
  730. }
  731. return nil
  732. }
  733. // ensureState ensures a state exists, creating it if needed
  734. func (p *StateParser) ensureState(id string) {
  735. if _, exists := p.stateMap[id]; !exists {
  736. state := &ast.StateNode{
  737. ID: id,
  738. Label: id,
  739. Type: ast.StateTypeDefault,
  740. SubStates: make(map[string]*ast.StateNode),
  741. CssClasses: make([]string, 0),
  742. }
  743. p.stateMap[id] = state
  744. p.diagram.States = append(p.diagram.States, state)
  745. }
  746. }
  747. // Helper methods
  748. func (p *StateParser) check(tokenType lexer.TokenType) bool {
  749. if p.isAtEnd() {
  750. return false
  751. }
  752. return p.peek().Type == tokenType
  753. }
  754. func (p *StateParser) checkNext(tokenType lexer.TokenType) bool {
  755. if p.current+1 >= len(p.tokens) {
  756. return false
  757. }
  758. return p.tokens[p.current+1].Type == tokenType
  759. }
  760. func (p *StateParser) checkKeyword(keyword string) bool {
  761. if p.isAtEnd() {
  762. return false
  763. }
  764. token := p.peek()
  765. return token.Type == lexer.TokenID && strings.ToLower(token.Value) == strings.ToLower(keyword)
  766. }
  767. func (p *StateParser) checkArrow() bool {
  768. token := p.peek()
  769. return token.Value == "-->" || token.Value == "--"
  770. }
  771. func (p *StateParser) advance() lexer.Token {
  772. if !p.isAtEnd() {
  773. p.current++
  774. }
  775. return p.previous()
  776. }
  777. func (p *StateParser) isAtEnd() bool {
  778. return p.current >= len(p.tokens) || p.peek().Type == lexer.TokenEOF
  779. }
  780. func (p *StateParser) peek() lexer.Token {
  781. if p.current >= len(p.tokens) {
  782. return lexer.Token{Type: lexer.TokenEOF}
  783. }
  784. return p.tokens[p.current]
  785. }
  786. func (p *StateParser) peekNext() lexer.Token {
  787. if p.current+1 >= len(p.tokens) {
  788. return lexer.Token{Type: lexer.TokenEOF}
  789. }
  790. return p.tokens[p.current+1]
  791. }
  792. func (p *StateParser) previous() lexer.Token {
  793. if p.current <= 0 {
  794. return lexer.Token{Type: lexer.TokenEOF}
  795. }
  796. return p.tokens[p.current-1]
  797. }
  798. func (p *StateParser) error(message string) error {
  799. token := p.peek()
  800. return fmt.Errorf("parse error at line %d, column %d: %s (got %s)",
  801. token.Line, token.Column, message, token.Type.String())
  802. }
  803. func (p *StateParser) skipToNextStatement() error {
  804. for !p.isAtEnd() && !p.check(lexer.TokenNewline) {
  805. p.advance()
  806. }
  807. if p.check(lexer.TokenNewline) {
  808. p.advance()
  809. }
  810. return nil
  811. }
  812. // parseStateAction parses state actions (entry, exit, do)
  813. func (p *StateParser) parseStateAction(actionType string) error {
  814. p.advance() // consume action type (entry/exit/do)
  815. // Expect colon
  816. if !p.check(lexer.TokenColon) {
  817. return p.error("expected ':' after " + actionType)
  818. }
  819. p.advance() // consume ':'
  820. // Parse state name
  821. if !p.check(lexer.TokenID) {
  822. return p.error("expected state name after " + actionType + " :")
  823. }
  824. stateName := p.advance().Value
  825. // Ensure state exists
  826. if _, exists := p.stateMap[stateName]; !exists {
  827. // Create state if it doesn't exist
  828. state := &ast.StateNode{
  829. ID: stateName,
  830. Label: stateName,
  831. Type: ast.StateTypeDefault,
  832. }
  833. p.stateMap[stateName] = state
  834. p.diagram.States = append(p.diagram.States, state)
  835. }
  836. // Parse action content (everything after state name until newline)
  837. var actionParts []string
  838. for !p.check(lexer.TokenNewline) && !p.isAtEnd() {
  839. actionParts = append(actionParts, p.advance().Value)
  840. }
  841. actionContent := strings.TrimSpace(strings.Join(actionParts, " "))
  842. // Set the appropriate action field
  843. state := p.stateMap[stateName]
  844. switch actionType {
  845. case "entry":
  846. state.EntryAction = &actionContent
  847. case "exit":
  848. state.ExitAction = &actionContent
  849. case "do":
  850. state.DoAction = &actionContent
  851. }
  852. return nil
  853. }