|
|
@@ -4,8 +4,7 @@ package parser
|
|
|
|
|
|
import (
|
|
|
"fmt"
|
|
|
- _ "strconv"
|
|
|
- _ "strings"
|
|
|
+ "strings"
|
|
|
|
|
|
"mermaid-go/pkg/ast"
|
|
|
"mermaid-go/pkg/lexer"
|
|
|
@@ -157,15 +156,93 @@ func (p *Parser) parseSubgraphStatement() error {
|
|
|
}
|
|
|
p.advance()
|
|
|
|
|
|
- // TODO: Implement subgraph parsing based on flow.jison
|
|
|
- // For now, skip to end
|
|
|
+ // Parse subgraph ID (optional)
|
|
|
+ var subgraphID string
|
|
|
+ var title string
|
|
|
+
|
|
|
+ if p.check(lexer.TokenID) {
|
|
|
+ subgraphID = p.advance().Value
|
|
|
+ } else if p.check(lexer.TokenString) {
|
|
|
+ // Quoted title becomes both ID and title
|
|
|
+ titleToken := p.advance().Value
|
|
|
+ title = titleToken[1 : len(titleToken)-1] // Remove quotes
|
|
|
+ subgraphID = title
|
|
|
+ }
|
|
|
+
|
|
|
+ // Check for explicit title in square brackets
|
|
|
+ if p.check(lexer.TokenOpenBracket) {
|
|
|
+ p.advance() // consume [
|
|
|
+ titleParts := make([]string, 0)
|
|
|
+ for !p.check(lexer.TokenCloseBracket) && !p.isAtEnd() {
|
|
|
+ titleParts = append(titleParts, p.advance().Value)
|
|
|
+ }
|
|
|
+ if p.check(lexer.TokenCloseBracket) {
|
|
|
+ p.advance() // consume ]
|
|
|
+ title = strings.Join(titleParts, "")
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ // Create subgraph
|
|
|
+ subgraph := &ast.FlowSubGraph{
|
|
|
+ ID: subgraphID,
|
|
|
+ Title: title,
|
|
|
+ LabelType: "text",
|
|
|
+ Classes: make([]string, 0),
|
|
|
+ Nodes: make([]string, 0),
|
|
|
+ }
|
|
|
+
|
|
|
+ // Store current parsing state
|
|
|
+ oldCurrent := p.current
|
|
|
+
|
|
|
+ // Parse subgraph content until 'end'
|
|
|
for !p.check(lexer.TokenEnd) && !p.isAtEnd() {
|
|
|
- p.advance()
|
|
|
+ if p.check(lexer.TokenNewline) {
|
|
|
+ p.advance()
|
|
|
+ continue
|
|
|
+ }
|
|
|
+
|
|
|
+ // Save state before parsing statement
|
|
|
+ beforeStatement := p.current
|
|
|
+
|
|
|
+ // Try to parse as edge statement (this will add vertices and edges)
|
|
|
+ err := p.parseEdgeStatement()
|
|
|
+ if err != nil {
|
|
|
+ // If edge parsing failed, skip to next statement
|
|
|
+ p.current = beforeStatement
|
|
|
+ p.skipToNextStatement()
|
|
|
+ continue
|
|
|
+ }
|
|
|
+
|
|
|
+ // Collect all vertices referenced in the statements parsed within this subgraph
|
|
|
+ for i := oldCurrent; i < p.current; i++ {
|
|
|
+ token := p.tokens[i]
|
|
|
+ if token.Type == lexer.TokenID {
|
|
|
+ // Check if this ID is a vertex
|
|
|
+ if vertex, exists := p.flowDB.vertices[token.Value]; exists {
|
|
|
+ // Add to subgraph nodes if not already present
|
|
|
+ found := false
|
|
|
+ for _, nodeID := range subgraph.Nodes {
|
|
|
+ if nodeID == vertex.ID {
|
|
|
+ found = true
|
|
|
+ break
|
|
|
+ }
|
|
|
+ }
|
|
|
+ if !found {
|
|
|
+ subgraph.Nodes = append(subgraph.Nodes, vertex.ID)
|
|
|
+ }
|
|
|
+ }
|
|
|
+ }
|
|
|
+ }
|
|
|
}
|
|
|
+
|
|
|
if p.check(lexer.TokenEnd) {
|
|
|
p.advance()
|
|
|
}
|
|
|
|
|
|
+ // Add subgraph to flowDB
|
|
|
+ p.flowDB.subGraphs = append(p.flowDB.subGraphs, subgraph)
|
|
|
+ p.flowDB.subGraphLookup[subgraphID] = subgraph
|
|
|
+
|
|
|
return nil
|
|
|
}
|
|
|
|
|
|
@@ -176,8 +253,38 @@ func (p *Parser) parseClassStatement() error {
|
|
|
}
|
|
|
p.advance()
|
|
|
|
|
|
- // Skip implementation for now
|
|
|
- return p.skipToNextStatement()
|
|
|
+ // Parse node list (comma separated)
|
|
|
+ nodeIDs := make([]string, 0)
|
|
|
+ for {
|
|
|
+ if !p.check(lexer.TokenID) {
|
|
|
+ break
|
|
|
+ }
|
|
|
+ nodeIDs = append(nodeIDs, p.advance().Value)
|
|
|
+
|
|
|
+ if p.check(lexer.TokenComma) {
|
|
|
+ p.advance() // consume comma
|
|
|
+ } else {
|
|
|
+ break
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ // Parse class name
|
|
|
+ if !p.check(lexer.TokenID) {
|
|
|
+ return p.error("expected class name")
|
|
|
+ }
|
|
|
+ className := p.advance().Value
|
|
|
+
|
|
|
+ // Apply class to nodes
|
|
|
+ for _, nodeID := range nodeIDs {
|
|
|
+ // Ensure vertex exists
|
|
|
+ if _, exists := p.flowDB.vertices[nodeID]; !exists {
|
|
|
+ p.addVertex(nodeID, "", "")
|
|
|
+ }
|
|
|
+ vertex := p.flowDB.vertices[nodeID]
|
|
|
+ vertex.Classes = append(vertex.Classes, className)
|
|
|
+ }
|
|
|
+
|
|
|
+ return nil
|
|
|
}
|
|
|
|
|
|
// parseClassDefStatement handles class definitions
|
|
|
@@ -187,8 +294,29 @@ func (p *Parser) parseClassDefStatement() error {
|
|
|
}
|
|
|
p.advance()
|
|
|
|
|
|
- // Skip implementation for now
|
|
|
- return p.skipToNextStatement()
|
|
|
+ // Parse class name
|
|
|
+ if !p.check(lexer.TokenID) {
|
|
|
+ return p.error("expected class name")
|
|
|
+ }
|
|
|
+ className := p.advance().Value
|
|
|
+
|
|
|
+ // Parse style definitions (everything until newline)
|
|
|
+ styles := make([]string, 0)
|
|
|
+ for !p.check(lexer.TokenNewline) && !p.isAtEnd() {
|
|
|
+ token := p.advance()
|
|
|
+ styles = append(styles, token.Value)
|
|
|
+ }
|
|
|
+
|
|
|
+ // Create class definition
|
|
|
+ class := &ast.FlowClass{
|
|
|
+ ID: className,
|
|
|
+ Styles: styles,
|
|
|
+ TextStyles: make([]string, 0),
|
|
|
+ }
|
|
|
+
|
|
|
+ p.flowDB.classes[className] = class
|
|
|
+
|
|
|
+ return nil
|
|
|
}
|
|
|
|
|
|
// parseStyleStatement handles style definitions
|
|
|
@@ -198,8 +326,29 @@ func (p *Parser) parseStyleStatement() error {
|
|
|
}
|
|
|
p.advance()
|
|
|
|
|
|
- // Skip implementation for now
|
|
|
- return p.skipToNextStatement()
|
|
|
+ // Parse node ID
|
|
|
+ if !p.check(lexer.TokenID) {
|
|
|
+ return p.error("expected node ID")
|
|
|
+ }
|
|
|
+ nodeID := p.advance().Value
|
|
|
+
|
|
|
+ // Parse style definitions (everything until newline)
|
|
|
+ styles := make([]string, 0)
|
|
|
+ for !p.check(lexer.TokenNewline) && !p.isAtEnd() {
|
|
|
+ token := p.advance()
|
|
|
+ styles = append(styles, token.Value)
|
|
|
+ }
|
|
|
+
|
|
|
+ // Ensure vertex exists
|
|
|
+ if _, exists := p.flowDB.vertices[nodeID]; !exists {
|
|
|
+ p.addVertex(nodeID, "", "")
|
|
|
+ }
|
|
|
+
|
|
|
+ // Apply styles to vertex
|
|
|
+ vertex := p.flowDB.vertices[nodeID]
|
|
|
+ vertex.Styles = append(vertex.Styles, styles...)
|
|
|
+
|
|
|
+ return nil
|
|
|
}
|
|
|
|
|
|
// parseLinkStyleStatement handles link style definitions
|
|
|
@@ -218,10 +367,52 @@ func (p *Parser) parseClickStatement() error {
|
|
|
if !p.check(lexer.TokenClick) {
|
|
|
return p.error("expected 'click'")
|
|
|
}
|
|
|
- p.advance()
|
|
|
+ p.advance() // consume 'click'
|
|
|
|
|
|
- // Skip implementation for now
|
|
|
- return p.skipToNextStatement()
|
|
|
+ // Parse node ID
|
|
|
+ if !p.check(lexer.TokenID) {
|
|
|
+ return p.error("expected node ID after 'click'")
|
|
|
+ }
|
|
|
+ nodeID := p.advance().Value
|
|
|
+
|
|
|
+ // Parse click action (callback or href)
|
|
|
+ clickEvent := &ast.ClickEvent{
|
|
|
+ NodeID: nodeID,
|
|
|
+ }
|
|
|
+
|
|
|
+ if p.check(lexer.TokenID) || p.check(lexer.TokenString) {
|
|
|
+ action := p.advance().Value
|
|
|
+
|
|
|
+ // Remove quotes if it's a string
|
|
|
+ if strings.HasPrefix(action, "\"") && strings.HasSuffix(action, "\"") {
|
|
|
+ action = action[1 : len(action)-1]
|
|
|
+ }
|
|
|
+
|
|
|
+ // Check if it's a callback (function call) or URL
|
|
|
+ if strings.Contains(action, "http") || strings.Contains(action, "www.") {
|
|
|
+ clickEvent.Link = &action
|
|
|
+ } else {
|
|
|
+ clickEvent.Callback = &action
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ // Parse optional target for links
|
|
|
+ if p.check(lexer.TokenString) {
|
|
|
+ target := p.advance().Value
|
|
|
+ target = target[1 : len(target)-1] // Remove quotes
|
|
|
+ clickEvent.Target = &target
|
|
|
+ }
|
|
|
+
|
|
|
+ // Apply click event to vertex
|
|
|
+ if vertex, exists := p.flowDB.vertices[nodeID]; exists {
|
|
|
+ vertex.OnClick = clickEvent
|
|
|
+ } else {
|
|
|
+ // Ensure vertex exists
|
|
|
+ p.addVertex(nodeID, "", "")
|
|
|
+ p.flowDB.vertices[nodeID].OnClick = clickEvent
|
|
|
+ }
|
|
|
+
|
|
|
+ return nil
|
|
|
}
|
|
|
|
|
|
// parseEdgeStatement parses edge definitions
|
|
|
@@ -298,8 +489,22 @@ func (p *Parser) parseShape() (ast.FlowVertexTypeParam, string, error) {
|
|
|
|
|
|
switch startToken.Type {
|
|
|
case lexer.TokenOpenBracket:
|
|
|
- shape = ast.VertexTypeRect
|
|
|
- endToken = lexer.TokenCloseBracket
|
|
|
+ // Check for special bracket shapes
|
|
|
+ if p.checkSequence([]lexer.TokenType{lexer.TokenSlash}) {
|
|
|
+ shape = ast.VertexTypeLeanRight
|
|
|
+ p.advance() // consume [
|
|
|
+ p.advance() // consume /
|
|
|
+ endToken = lexer.TokenSlash
|
|
|
+ } else if p.checkSequence([]lexer.TokenType{lexer.TokenBackslash}) {
|
|
|
+ shape = ast.VertexTypeLeanLeft
|
|
|
+ p.advance() // consume [
|
|
|
+ p.advance() // consume \
|
|
|
+ endToken = lexer.TokenBackslash
|
|
|
+ } else {
|
|
|
+ shape = ast.VertexTypeRect
|
|
|
+ endToken = lexer.TokenCloseBracket
|
|
|
+ p.advance() // consume [
|
|
|
+ }
|
|
|
case lexer.TokenOpenParen:
|
|
|
if p.checkNext(lexer.TokenOpenParen) { // ((text))
|
|
|
shape = ast.VertexTypeCircle
|
|
|
@@ -308,25 +513,36 @@ func (p *Parser) parseShape() (ast.FlowVertexTypeParam, string, error) {
|
|
|
endToken = lexer.TokenCloseParen
|
|
|
} else { // (text)
|
|
|
shape = ast.VertexTypeRound
|
|
|
+ p.advance() // consume (
|
|
|
endToken = lexer.TokenCloseParen
|
|
|
}
|
|
|
case lexer.TokenOpenBrace:
|
|
|
shape = ast.VertexTypeDiamond
|
|
|
+ p.advance() // consume {
|
|
|
endToken = lexer.TokenCloseBrace
|
|
|
case lexer.TokenOpenDoubleParen:
|
|
|
shape = ast.VertexTypeCircle
|
|
|
+ p.advance() // consume ((
|
|
|
endToken = lexer.TokenCloseDoubleParen
|
|
|
+ case lexer.TokenCloseAngle:
|
|
|
+ // Check for flag shape >text]
|
|
|
+ shape = ast.VertexTypeFlag
|
|
|
+ p.advance() // consume >
|
|
|
+ endToken = lexer.TokenCloseBracket
|
|
|
default:
|
|
|
return "", "", p.error("expected shape delimiter")
|
|
|
}
|
|
|
|
|
|
- if shape != ast.VertexTypeCircle || startToken.Type != lexer.TokenOpenDoubleParen {
|
|
|
- p.advance() // consume opening delimiter
|
|
|
- }
|
|
|
-
|
|
|
// Parse text content
|
|
|
text := ""
|
|
|
for !p.check(endToken) && !p.isAtEnd() {
|
|
|
+ if endToken == lexer.TokenSlash && p.check(lexer.TokenSlash) {
|
|
|
+ break
|
|
|
+ }
|
|
|
+ if endToken == lexer.TokenBackslash && p.check(lexer.TokenBackslash) {
|
|
|
+ break
|
|
|
+ }
|
|
|
+
|
|
|
if p.check(lexer.TokenString) {
|
|
|
// Remove quotes from string
|
|
|
val := p.advance().Value
|
|
|
@@ -336,17 +552,47 @@ func (p *Parser) parseShape() (ast.FlowVertexTypeParam, string, error) {
|
|
|
}
|
|
|
}
|
|
|
|
|
|
- if !p.check(endToken) {
|
|
|
- return "", "", p.error(fmt.Sprintf("expected closing delimiter"))
|
|
|
- }
|
|
|
- p.advance() // consume closing delimiter
|
|
|
+ // Consume closing delimiter(s)
|
|
|
+ switch endToken {
|
|
|
+ case lexer.TokenSlash:
|
|
|
+ if !p.check(lexer.TokenSlash) {
|
|
|
+ return "", "", p.error("expected closing /")
|
|
|
+ }
|
|
|
+ p.advance() // consume /
|
|
|
+ if !p.check(lexer.TokenCloseBracket) {
|
|
|
+ return "", "", p.error("expected closing ]")
|
|
|
+ }
|
|
|
+ p.advance() // consume ]
|
|
|
|
|
|
- // Handle double paren closing
|
|
|
- if shape == ast.VertexTypeCircle && endToken == lexer.TokenCloseParen {
|
|
|
- if !p.check(lexer.TokenCloseParen) {
|
|
|
- return "", "", p.error("expected second closing parenthesis")
|
|
|
+ case lexer.TokenBackslash:
|
|
|
+ if !p.check(lexer.TokenBackslash) {
|
|
|
+ return "", "", p.error("expected closing \\")
|
|
|
}
|
|
|
- p.advance()
|
|
|
+ p.advance() // consume \
|
|
|
+ if !p.check(lexer.TokenCloseBracket) {
|
|
|
+ return "", "", p.error("expected closing ]")
|
|
|
+ }
|
|
|
+ p.advance() // consume ]
|
|
|
+
|
|
|
+ case lexer.TokenCloseParen:
|
|
|
+ if !p.check(endToken) {
|
|
|
+ return "", "", p.error("expected closing delimiter")
|
|
|
+ }
|
|
|
+ p.advance() // consume closing delimiter
|
|
|
+
|
|
|
+ // Handle double paren closing
|
|
|
+ if shape == ast.VertexTypeCircle && startToken.Type == lexer.TokenOpenParen {
|
|
|
+ if !p.check(lexer.TokenCloseParen) {
|
|
|
+ return "", "", p.error("expected second closing parenthesis")
|
|
|
+ }
|
|
|
+ p.advance()
|
|
|
+ }
|
|
|
+
|
|
|
+ default:
|
|
|
+ if !p.check(endToken) {
|
|
|
+ return "", "", p.error("expected closing delimiter")
|
|
|
+ }
|
|
|
+ p.advance() // consume closing delimiter
|
|
|
}
|
|
|
|
|
|
return shape, text, nil
|
|
|
@@ -496,6 +742,19 @@ func (p *Parser) checkNext(tokenType lexer.TokenType) bool {
|
|
|
return p.tokens[p.current+1].Type == tokenType
|
|
|
}
|
|
|
|
|
|
+// checkSequence checks if the current position plus offset matches a sequence of token types
|
|
|
+func (p *Parser) checkSequence(types []lexer.TokenType) bool {
|
|
|
+ for i, tokenType := range types {
|
|
|
+ if p.current+1+i >= len(p.tokens) {
|
|
|
+ return false
|
|
|
+ }
|
|
|
+ if p.tokens[p.current+1+i].Type != tokenType {
|
|
|
+ return false
|
|
|
+ }
|
|
|
+ }
|
|
|
+ return true
|
|
|
+}
|
|
|
+
|
|
|
// checkDirection returns true if current token is a direction
|
|
|
func (p *Parser) checkDirection() bool {
|
|
|
if p.isAtEnd() {
|
|
|
@@ -514,7 +773,8 @@ func (p *Parser) checkShapeStart() bool {
|
|
|
}
|
|
|
tokenType := p.peek().Type
|
|
|
return tokenType == lexer.TokenOpenBracket || tokenType == lexer.TokenOpenParen ||
|
|
|
- tokenType == lexer.TokenOpenBrace || tokenType == lexer.TokenOpenDoubleParen
|
|
|
+ tokenType == lexer.TokenOpenBrace || tokenType == lexer.TokenOpenDoubleParen ||
|
|
|
+ tokenType == lexer.TokenCloseAngle // for flag shape >text]
|
|
|
}
|
|
|
|
|
|
// checkArrow returns true if current token is an arrow
|