|
|
@@ -13,18 +13,24 @@ import (
|
|
|
type C4Parser struct {
|
|
|
tokens []lexer.Token
|
|
|
current int
|
|
|
- diagram *ast.C4Diagram
|
|
|
+ diagram C4DiagramInterface
|
|
|
+}
|
|
|
+
|
|
|
+// C4DiagramInterface is an interface for C4 diagrams
|
|
|
+type C4DiagramInterface interface {
|
|
|
+ ast.Diagram
|
|
|
+ AddElement(element *ast.C4Element)
|
|
|
+ AddRelation(relation *ast.C4Relation)
|
|
|
+ AddBoundary(boundary *ast.C4Boundary)
|
|
|
}
|
|
|
|
|
|
// NewC4Parser creates a new C4 parser
|
|
|
func NewC4Parser() *C4Parser {
|
|
|
- return &C4Parser{
|
|
|
- diagram: ast.NewC4Diagram(),
|
|
|
- }
|
|
|
+ return &C4Parser{}
|
|
|
}
|
|
|
|
|
|
-// Parse parses C4 diagram syntax
|
|
|
-func (p *C4Parser) Parse(input string) (*ast.C4Diagram, error) {
|
|
|
+// Parse parses C4 syntax and returns the appropriate C4 diagram
|
|
|
+func (p *C4Parser) Parse(input string) (ast.Diagram, error) {
|
|
|
// Tokenize
|
|
|
l := lexer.NewLexer(input)
|
|
|
tokens, err := l.Tokenize()
|
|
|
@@ -35,41 +41,49 @@ func (p *C4Parser) Parse(input string) (*ast.C4Diagram, error) {
|
|
|
// Filter tokens
|
|
|
p.tokens = lexer.FilterTokens(tokens)
|
|
|
p.current = 0
|
|
|
- p.diagram = ast.NewC4Diagram()
|
|
|
|
|
|
- // Parse document
|
|
|
- err = p.parseDocument()
|
|
|
+ // Parse document header to determine diagram type
|
|
|
+ err = p.parseDocumentHeader()
|
|
|
if err != nil {
|
|
|
return nil, fmt.Errorf("syntax analysis failed: %w", err)
|
|
|
}
|
|
|
|
|
|
+ // Parse statements
|
|
|
+ for !p.isAtEnd() {
|
|
|
+ if err := p.parseStatement(); err != nil {
|
|
|
+ return nil, err
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
return p.diagram, nil
|
|
|
}
|
|
|
|
|
|
-// parseDocument parses the C4 diagram document
|
|
|
-func (p *C4Parser) parseDocument() error {
|
|
|
- // Expect C4Context, C4Container, or C4Component
|
|
|
+// parseDocumentHeader parses the C4 document header to determine diagram type
|
|
|
+func (p *C4Parser) parseDocumentHeader() error {
|
|
|
if !p.check(lexer.TokenID) {
|
|
|
return p.error("expected C4 diagram type")
|
|
|
}
|
|
|
|
|
|
- diagramType := p.peek().Value
|
|
|
- if diagramType != "C4Context" && diagramType != "C4Container" && diagramType != "C4Component" {
|
|
|
- return p.error("expected 'C4Context', 'C4Container', or 'C4Component'")
|
|
|
- }
|
|
|
- p.advance()
|
|
|
-
|
|
|
- // Parse statements
|
|
|
- for !p.isAtEnd() {
|
|
|
- if err := p.parseStatement(); err != nil {
|
|
|
- return err
|
|
|
- }
|
|
|
+ diagramType := p.advance().Value
|
|
|
+ switch strings.ToLower(diagramType) {
|
|
|
+ case "c4context":
|
|
|
+ p.diagram = ast.NewC4ContextDiagram()
|
|
|
+ case "c4container":
|
|
|
+ p.diagram = ast.NewC4ContainerDiagram()
|
|
|
+ case "c4component":
|
|
|
+ p.diagram = ast.NewC4ComponentDiagram()
|
|
|
+ case "c4dynamic":
|
|
|
+ p.diagram = ast.NewC4DynamicDiagram()
|
|
|
+ case "c4deployment":
|
|
|
+ p.diagram = ast.NewC4DeploymentDiagram()
|
|
|
+ default:
|
|
|
+ return p.error("unsupported C4 diagram type: " + diagramType)
|
|
|
}
|
|
|
|
|
|
return nil
|
|
|
}
|
|
|
|
|
|
-// parseStatement parses individual C4 diagram statements
|
|
|
+// parseStatement parses individual C4 statements
|
|
|
func (p *C4Parser) parseStatement() error {
|
|
|
if p.isAtEnd() {
|
|
|
return nil
|
|
|
@@ -81,13 +95,32 @@ func (p *C4Parser) parseStatement() error {
|
|
|
return nil
|
|
|
case p.checkKeyword("title"):
|
|
|
return p.parseTitle()
|
|
|
- case p.checkKeyword("Boundary"):
|
|
|
- return p.parseBoundary()
|
|
|
+ case p.checkKeyword("person"):
|
|
|
+ return p.parsePerson()
|
|
|
+ case p.checkKeyword("system"):
|
|
|
+ return p.parseSystem()
|
|
|
+ case p.checkKeyword("container"):
|
|
|
+ return p.parseContainer()
|
|
|
+ case p.checkKeyword("component"):
|
|
|
+ return p.parseComponent()
|
|
|
+ case p.checkKeyword("system_boundary"):
|
|
|
+ return p.parseSystemBoundary()
|
|
|
+ case p.checkKeyword("container_boundary"):
|
|
|
+ return p.parseContainerBoundary()
|
|
|
+ case p.checkKeyword("system_ext"):
|
|
|
+ return p.parseSystemExt()
|
|
|
+ case p.checkKeyword("container_ext"):
|
|
|
+ return p.parseContainerExt()
|
|
|
+ case p.checkKeyword("systemdb"):
|
|
|
+ return p.parseSystemDb()
|
|
|
+ case p.checkKeyword("containerdb"):
|
|
|
+ return p.parseContainerDb()
|
|
|
+ case p.checkKeyword("containerqueue"):
|
|
|
+ return p.parseContainerQueue()
|
|
|
+ case p.check(lexer.TokenID):
|
|
|
+ // Could be a relation
|
|
|
+ return p.parseRelation()
|
|
|
default:
|
|
|
- // Try to parse as element or relation definition
|
|
|
- if p.check(lexer.TokenID) {
|
|
|
- return p.parseElementOrRelation()
|
|
|
- }
|
|
|
token := p.peek()
|
|
|
return p.error(fmt.Sprintf("unexpected token: %s", token.Value))
|
|
|
}
|
|
|
@@ -104,115 +137,478 @@ func (p *C4Parser) parseTitle() error {
|
|
|
|
|
|
if len(titleParts) > 0 {
|
|
|
title := strings.TrimSpace(strings.Join(titleParts, " "))
|
|
|
- p.diagram.Title = &title
|
|
|
+ // Set title on the diagram
|
|
|
+ if ctx, ok := p.diagram.(*ast.C4ContextDiagram); ok {
|
|
|
+ ctx.Title = &title
|
|
|
+ } else if container, ok := p.diagram.(*ast.C4ContainerDiagram); ok {
|
|
|
+ container.Title = &title
|
|
|
+ } else if component, ok := p.diagram.(*ast.C4ComponentDiagram); ok {
|
|
|
+ component.Title = &title
|
|
|
+ } else if dynamic, ok := p.diagram.(*ast.C4DynamicDiagram); ok {
|
|
|
+ dynamic.Title = &title
|
|
|
+ } else if deployment, ok := p.diagram.(*ast.C4DeploymentDiagram); ok {
|
|
|
+ deployment.Title = &title
|
|
|
+ }
|
|
|
}
|
|
|
|
|
|
return nil
|
|
|
}
|
|
|
|
|
|
-// parseBoundary parses boundary definitions
|
|
|
-func (p *C4Parser) parseBoundary() error {
|
|
|
- p.advance() // consume 'Boundary'
|
|
|
+// parsePerson parses Person statements
|
|
|
+func (p *C4Parser) parsePerson() error {
|
|
|
+ p.advance() // consume 'person'
|
|
|
+
|
|
|
+ if !p.check(lexer.TokenOpenParen) {
|
|
|
+ return p.error("expected '(' after Person")
|
|
|
+ }
|
|
|
+ p.advance() // consume '('
|
|
|
|
|
|
if !p.check(lexer.TokenID) {
|
|
|
- return p.error("expected boundary ID")
|
|
|
+ return p.error("expected Person ID")
|
|
|
}
|
|
|
+ personID := p.advance().Value
|
|
|
|
|
|
- id := p.advance().Value
|
|
|
- label := id // Default label is the ID
|
|
|
+ if !p.check(lexer.TokenComma) {
|
|
|
+ return p.error("expected ',' after Person ID")
|
|
|
+ }
|
|
|
+ p.advance() // consume ','
|
|
|
|
|
|
- // Parse label if specified
|
|
|
- if p.check(lexer.TokenString) {
|
|
|
- label = p.advance().Value
|
|
|
- // Remove quotes
|
|
|
- if strings.HasPrefix(label, "\"") && strings.HasSuffix(label, "\"") {
|
|
|
- label = label[1 : len(label)-1]
|
|
|
+ // Parse name (quoted string)
|
|
|
+ var nameParts []string
|
|
|
+ for !p.check(lexer.TokenComma) && !p.check(lexer.TokenCloseParen) && !p.isAtEnd() {
|
|
|
+ nameParts = append(nameParts, p.advance().Value)
|
|
|
+ }
|
|
|
+ name := strings.Trim(strings.Join(nameParts, " "), "\"")
|
|
|
+
|
|
|
+ // Parse description (optional)
|
|
|
+ var description string
|
|
|
+ if p.check(lexer.TokenComma) {
|
|
|
+ p.advance() // consume ','
|
|
|
+ var descParts []string
|
|
|
+ for !p.check(lexer.TokenCloseParen) && !p.isAtEnd() {
|
|
|
+ descParts = append(descParts, p.advance().Value)
|
|
|
}
|
|
|
+ description = strings.Trim(strings.Join(descParts, " "), "\"")
|
|
|
+ }
|
|
|
+
|
|
|
+ if !p.check(lexer.TokenCloseParen) {
|
|
|
+ return p.error("expected ')' to close Person")
|
|
|
}
|
|
|
+ p.advance() // consume ')'
|
|
|
|
|
|
element := &ast.C4Element{
|
|
|
- ID: id,
|
|
|
- Label: label,
|
|
|
- Type: ast.C4ElementBoundary,
|
|
|
+ ID: personID,
|
|
|
+ Type: ast.C4ElementPerson,
|
|
|
+ Name: name,
|
|
|
+ Description: description,
|
|
|
}
|
|
|
|
|
|
p.diagram.AddElement(element)
|
|
|
return nil
|
|
|
}
|
|
|
|
|
|
-// parseElementOrRelation parses element or relation definitions
|
|
|
-func (p *C4Parser) parseElementOrRelation() error {
|
|
|
- // Parse element ID
|
|
|
- elementID := p.advance().Value
|
|
|
+// parseSystem parses System statements
|
|
|
+func (p *C4Parser) parseSystem() error {
|
|
|
+ p.advance() // consume 'system'
|
|
|
+
|
|
|
+ if !p.check(lexer.TokenOpenParen) {
|
|
|
+ return p.error("expected '(' after System")
|
|
|
+ }
|
|
|
+ p.advance() // consume '('
|
|
|
+
|
|
|
+ if !p.check(lexer.TokenID) {
|
|
|
+ return p.error("expected System ID")
|
|
|
+ }
|
|
|
+ systemID := p.advance().Value
|
|
|
|
|
|
- // Check if this is a relation (has arrow)
|
|
|
- if p.check(lexer.TokenArrowSolid) || p.check(lexer.TokenArrowDotted) {
|
|
|
- return p.parseRelation(elementID)
|
|
|
+ if !p.check(lexer.TokenComma) {
|
|
|
+ return p.error("expected ',' after System ID")
|
|
|
+ }
|
|
|
+ p.advance() // consume ','
|
|
|
+
|
|
|
+ // Parse name (quoted string)
|
|
|
+ var nameParts []string
|
|
|
+ for !p.check(lexer.TokenComma) && !p.check(lexer.TokenCloseParen) && !p.isAtEnd() {
|
|
|
+ nameParts = append(nameParts, p.advance().Value)
|
|
|
+ }
|
|
|
+ name := strings.Trim(strings.Join(nameParts, " "), "\"")
|
|
|
+
|
|
|
+ // Parse description (optional)
|
|
|
+ var description string
|
|
|
+ if p.check(lexer.TokenComma) {
|
|
|
+ p.advance() // consume ','
|
|
|
+ var descParts []string
|
|
|
+ for !p.check(lexer.TokenCloseParen) && !p.isAtEnd() {
|
|
|
+ descParts = append(descParts, p.advance().Value)
|
|
|
+ }
|
|
|
+ description = strings.Trim(strings.Join(descParts, " "), "\"")
|
|
|
+ }
|
|
|
+
|
|
|
+ if !p.check(lexer.TokenCloseParen) {
|
|
|
+ return p.error("expected ')' to close System")
|
|
|
+ }
|
|
|
+ p.advance() // consume ')'
|
|
|
+
|
|
|
+ element := &ast.C4Element{
|
|
|
+ ID: systemID,
|
|
|
+ Type: ast.C4ElementSystem,
|
|
|
+ Name: name,
|
|
|
+ Description: description,
|
|
|
}
|
|
|
|
|
|
- // Otherwise, parse as element definition
|
|
|
- return p.parseElement(elementID)
|
|
|
+ p.diagram.AddElement(element)
|
|
|
+ return nil
|
|
|
}
|
|
|
|
|
|
-// parseElement parses element definitions
|
|
|
-func (p *C4Parser) parseElement(id string) error {
|
|
|
- label := id // Default label is the ID
|
|
|
- elementType := ast.C4ElementSystem // Default type
|
|
|
-
|
|
|
- // Parse type if specified
|
|
|
- if p.checkKeyword("Person") || p.checkKeyword("Person_Ext") ||
|
|
|
- p.checkKeyword("System") || p.checkKeyword("System_Ext") ||
|
|
|
- p.checkKeyword("SystemDb") || p.checkKeyword("Container") ||
|
|
|
- p.checkKeyword("Container_Ext") || p.checkKeyword("ContainerDb") ||
|
|
|
- p.checkKeyword("Component") || p.checkKeyword("Component_Ext") ||
|
|
|
- p.checkKeyword("ComponentDb") {
|
|
|
- typeStr := p.advance().Value
|
|
|
- elementType = ast.C4ElementType(strings.ToLower(typeStr))
|
|
|
- }
|
|
|
-
|
|
|
- // Parse label if specified
|
|
|
- if p.check(lexer.TokenString) {
|
|
|
- label = p.advance().Value
|
|
|
- // Remove quotes
|
|
|
- if strings.HasPrefix(label, "\"") && strings.HasSuffix(label, "\"") {
|
|
|
- label = label[1 : len(label)-1]
|
|
|
+// parseContainer parses Container statements
|
|
|
+func (p *C4Parser) parseContainer() error {
|
|
|
+ p.advance() // consume 'container'
|
|
|
+
|
|
|
+ if !p.check(lexer.TokenOpenParen) {
|
|
|
+ return p.error("expected '(' after Container")
|
|
|
+ }
|
|
|
+ p.advance() // consume '('
|
|
|
+
|
|
|
+ if !p.check(lexer.TokenID) {
|
|
|
+ return p.error("expected Container ID")
|
|
|
+ }
|
|
|
+ containerID := p.advance().Value
|
|
|
+
|
|
|
+ if !p.check(lexer.TokenComma) {
|
|
|
+ return p.error("expected ',' after Container ID")
|
|
|
+ }
|
|
|
+ p.advance() // consume ','
|
|
|
+
|
|
|
+ // Parse name (quoted string)
|
|
|
+ var nameParts []string
|
|
|
+ for !p.check(lexer.TokenComma) && !p.check(lexer.TokenCloseParen) && !p.isAtEnd() {
|
|
|
+ nameParts = append(nameParts, p.advance().Value)
|
|
|
+ }
|
|
|
+ name := strings.Trim(strings.Join(nameParts, " "), "\"")
|
|
|
+
|
|
|
+ // Parse technology (optional)
|
|
|
+ var technology string
|
|
|
+ if p.check(lexer.TokenComma) {
|
|
|
+ p.advance() // consume ','
|
|
|
+ var techParts []string
|
|
|
+ for !p.check(lexer.TokenComma) && !p.check(lexer.TokenCloseParen) && !p.isAtEnd() {
|
|
|
+ techParts = append(techParts, p.advance().Value)
|
|
|
}
|
|
|
+ technology = strings.Trim(strings.Join(techParts, " "), "\"")
|
|
|
}
|
|
|
|
|
|
+ // Parse description (optional)
|
|
|
+ var description string
|
|
|
+ if p.check(lexer.TokenComma) {
|
|
|
+ p.advance() // consume ','
|
|
|
+ var descParts []string
|
|
|
+ for !p.check(lexer.TokenCloseParen) && !p.isAtEnd() {
|
|
|
+ descParts = append(descParts, p.advance().Value)
|
|
|
+ }
|
|
|
+ description = strings.Trim(strings.Join(descParts, " "), "\"")
|
|
|
+ }
|
|
|
+
|
|
|
+ if !p.check(lexer.TokenCloseParen) {
|
|
|
+ return p.error("expected ')' to close Container")
|
|
|
+ }
|
|
|
+ p.advance() // consume ')'
|
|
|
+
|
|
|
element := &ast.C4Element{
|
|
|
- ID: id,
|
|
|
- Label: label,
|
|
|
- Type: elementType,
|
|
|
+ ID: containerID,
|
|
|
+ Type: ast.C4ElementContainer,
|
|
|
+ Name: name,
|
|
|
+ Description: description,
|
|
|
+ }
|
|
|
+
|
|
|
+ if technology != "" {
|
|
|
+ element.Technology = &technology
|
|
|
}
|
|
|
|
|
|
p.diagram.AddElement(element)
|
|
|
return nil
|
|
|
}
|
|
|
|
|
|
-// parseRelation parses relation definitions
|
|
|
-func (p *C4Parser) parseRelation(from string) error {
|
|
|
- // Parse arrow type
|
|
|
- p.advance() // consume arrow
|
|
|
+// parseComponent parses Component statements
|
|
|
+func (p *C4Parser) parseComponent() error {
|
|
|
+ p.advance() // consume 'component'
|
|
|
+
|
|
|
+ if !p.check(lexer.TokenOpenParen) {
|
|
|
+ return p.error("expected '(' after Component")
|
|
|
+ }
|
|
|
+ p.advance() // consume '('
|
|
|
|
|
|
- // Parse target element
|
|
|
if !p.check(lexer.TokenID) {
|
|
|
- return p.error("expected target element ID")
|
|
|
+ return p.error("expected Component ID")
|
|
|
}
|
|
|
- to := p.advance().Value
|
|
|
+ componentID := p.advance().Value
|
|
|
|
|
|
- relation := &ast.C4Relation{
|
|
|
- From: from,
|
|
|
- To: to,
|
|
|
+ if !p.check(lexer.TokenComma) {
|
|
|
+ return p.error("expected ',' after Component ID")
|
|
|
+ }
|
|
|
+ p.advance() // consume ','
|
|
|
+
|
|
|
+ // Parse name (quoted string)
|
|
|
+ var nameParts []string
|
|
|
+ for !p.check(lexer.TokenComma) && !p.check(lexer.TokenCloseParen) && !p.isAtEnd() {
|
|
|
+ nameParts = append(nameParts, p.advance().Value)
|
|
|
+ }
|
|
|
+ name := strings.Trim(strings.Join(nameParts, " "), "\"")
|
|
|
+
|
|
|
+ // Parse technology (optional)
|
|
|
+ var technology string
|
|
|
+ if p.check(lexer.TokenComma) {
|
|
|
+ p.advance() // consume ','
|
|
|
+ var techParts []string
|
|
|
+ for !p.check(lexer.TokenComma) && !p.check(lexer.TokenCloseParen) && !p.isAtEnd() {
|
|
|
+ techParts = append(techParts, p.advance().Value)
|
|
|
+ }
|
|
|
+ technology = strings.Trim(strings.Join(techParts, " "), "\"")
|
|
|
+ }
|
|
|
+
|
|
|
+ // Parse description (optional)
|
|
|
+ var description string
|
|
|
+ if p.check(lexer.TokenComma) {
|
|
|
+ p.advance() // consume ','
|
|
|
+ var descParts []string
|
|
|
+ for !p.check(lexer.TokenCloseParen) && !p.isAtEnd() {
|
|
|
+ descParts = append(descParts, p.advance().Value)
|
|
|
+ }
|
|
|
+ description = strings.Trim(strings.Join(descParts, " "), "\"")
|
|
|
+ }
|
|
|
+
|
|
|
+ if !p.check(lexer.TokenCloseParen) {
|
|
|
+ return p.error("expected ')' to close Component")
|
|
|
+ }
|
|
|
+ p.advance() // consume ')'
|
|
|
+
|
|
|
+ element := &ast.C4Element{
|
|
|
+ ID: componentID,
|
|
|
+ Type: ast.C4ElementComponent,
|
|
|
+ Name: name,
|
|
|
+ Description: description,
|
|
|
+ }
|
|
|
+
|
|
|
+ if technology != "" {
|
|
|
+ element.Technology = &technology
|
|
|
+ }
|
|
|
+
|
|
|
+ p.diagram.AddElement(element)
|
|
|
+ return nil
|
|
|
+}
|
|
|
+
|
|
|
+// parseSystemBoundary parses System_Boundary statements
|
|
|
+func (p *C4Parser) parseSystemBoundary() error {
|
|
|
+ p.advance() // consume 'system_boundary'
|
|
|
+
|
|
|
+ if !p.check(lexer.TokenOpenParen) {
|
|
|
+ return p.error("expected '(' after System_Boundary")
|
|
|
+ }
|
|
|
+ p.advance() // consume '('
|
|
|
+
|
|
|
+ if !p.check(lexer.TokenID) {
|
|
|
+ return p.error("expected System_Boundary ID")
|
|
|
+ }
|
|
|
+ boundaryID := p.advance().Value
|
|
|
+
|
|
|
+ if !p.check(lexer.TokenComma) {
|
|
|
+ return p.error("expected ',' after System_Boundary ID")
|
|
|
+ }
|
|
|
+ p.advance() // consume ','
|
|
|
+
|
|
|
+ // Parse name (quoted string)
|
|
|
+ var nameParts []string
|
|
|
+ for !p.check(lexer.TokenComma) && !p.check(lexer.TokenCloseParen) && !p.check(lexer.TokenOpenBrace) && !p.isAtEnd() {
|
|
|
+ nameParts = append(nameParts, p.advance().Value)
|
|
|
+ }
|
|
|
+ name := strings.Trim(strings.Join(nameParts, " "), "\"")
|
|
|
+
|
|
|
+ // Skip comma if present
|
|
|
+ if p.check(lexer.TokenComma) {
|
|
|
+ p.advance() // consume ','
|
|
|
+ }
|
|
|
+
|
|
|
+ // Check if there's a closing parenthesis before the brace
|
|
|
+ if p.check(lexer.TokenCloseParen) {
|
|
|
+ p.advance() // consume ')'
|
|
|
}
|
|
|
|
|
|
- // Parse label if specified
|
|
|
- if p.check(lexer.TokenString) {
|
|
|
- label := p.advance().Value
|
|
|
- // Remove quotes
|
|
|
- if strings.HasPrefix(label, "\"") && strings.HasSuffix(label, "\"") {
|
|
|
- label = label[1 : len(label)-1]
|
|
|
+ if !p.check(lexer.TokenOpenBrace) {
|
|
|
+ return p.error("expected '{' to start System_Boundary")
|
|
|
+ }
|
|
|
+ p.advance() // consume '{'
|
|
|
+
|
|
|
+ boundary := &ast.C4Boundary{
|
|
|
+ ID: boundaryID,
|
|
|
+ Type: ast.C4BoundarySystem,
|
|
|
+ Name: name,
|
|
|
+ Elements: make([]string, 0),
|
|
|
+ }
|
|
|
+
|
|
|
+ // Parse elements within boundary
|
|
|
+ for !p.check(lexer.TokenCloseBrace) && !p.isAtEnd() {
|
|
|
+ if err := p.parseStatement(); err != nil {
|
|
|
+ return err
|
|
|
+ }
|
|
|
+ // Add the last parsed element to this boundary
|
|
|
+ if len(p.diagram.(C4DiagramInterface).(*ast.C4ContextDiagram).Elements) > 0 {
|
|
|
+ lastElement := p.diagram.(C4DiagramInterface).(*ast.C4ContextDiagram).Elements[len(p.diagram.(C4DiagramInterface).(*ast.C4ContextDiagram).Elements)-1]
|
|
|
+ boundary.Elements = append(boundary.Elements, lastElement.ID)
|
|
|
}
|
|
|
- relation.Label = &label
|
|
|
+ }
|
|
|
+
|
|
|
+ if !p.check(lexer.TokenCloseBrace) {
|
|
|
+ return p.error("expected '}' to close System_Boundary")
|
|
|
+ }
|
|
|
+ p.advance() // consume '}'
|
|
|
+
|
|
|
+ p.diagram.AddBoundary(boundary)
|
|
|
+ return nil
|
|
|
+}
|
|
|
+
|
|
|
+// parseContainerBoundary parses Container_Boundary statements
|
|
|
+func (p *C4Parser) parseContainerBoundary() error {
|
|
|
+ // Similar to parseSystemBoundary but for containers
|
|
|
+ return p.parseSystemBoundary() // Simplified for now
|
|
|
+}
|
|
|
+
|
|
|
+// parseSystemExt parses System_Ext statements
|
|
|
+func (p *C4Parser) parseSystemExt() error {
|
|
|
+ // Similar to parseSystem but marked as external
|
|
|
+ return p.parseSystem() // Simplified for now
|
|
|
+}
|
|
|
+
|
|
|
+// parseContainerExt parses Container_Ext statements
|
|
|
+func (p *C4Parser) parseContainerExt() error {
|
|
|
+ // Similar to parseContainer but marked as external
|
|
|
+ return p.parseContainer() // Simplified for now
|
|
|
+}
|
|
|
+
|
|
|
+// parseSystemDb parses SystemDb statements
|
|
|
+func (p *C4Parser) parseSystemDb() error {
|
|
|
+ p.advance() // consume 'systemdb'
|
|
|
+
|
|
|
+ if !p.check(lexer.TokenOpenParen) {
|
|
|
+ return p.error("expected '(' after SystemDb")
|
|
|
+ }
|
|
|
+ p.advance() // consume '('
|
|
|
+
|
|
|
+ if !p.check(lexer.TokenID) {
|
|
|
+ return p.error("expected SystemDb ID")
|
|
|
+ }
|
|
|
+ dbID := p.advance().Value
|
|
|
+
|
|
|
+ if !p.check(lexer.TokenComma) {
|
|
|
+ return p.error("expected ',' after SystemDb ID")
|
|
|
+ }
|
|
|
+ p.advance() // consume ','
|
|
|
+
|
|
|
+ // Parse name (quoted string)
|
|
|
+ var nameParts []string
|
|
|
+ for !p.check(lexer.TokenComma) && !p.check(lexer.TokenCloseParen) && !p.isAtEnd() {
|
|
|
+ nameParts = append(nameParts, p.advance().Value)
|
|
|
+ }
|
|
|
+ name := strings.Trim(strings.Join(nameParts, " "), "\"")
|
|
|
+
|
|
|
+ // Parse description (optional)
|
|
|
+ var description string
|
|
|
+ if p.check(lexer.TokenComma) {
|
|
|
+ p.advance() // consume ','
|
|
|
+ var descParts []string
|
|
|
+ for !p.check(lexer.TokenComma) && !p.check(lexer.TokenCloseParen) && !p.isAtEnd() {
|
|
|
+ descParts = append(descParts, p.advance().Value)
|
|
|
+ }
|
|
|
+ description = strings.Trim(strings.Join(descParts, " "), "\"")
|
|
|
+ }
|
|
|
+
|
|
|
+ // Parse technology (optional)
|
|
|
+ var technology string
|
|
|
+ if p.check(lexer.TokenComma) {
|
|
|
+ p.advance() // consume ','
|
|
|
+ var techParts []string
|
|
|
+ for !p.check(lexer.TokenCloseParen) && !p.isAtEnd() {
|
|
|
+ techParts = append(techParts, p.advance().Value)
|
|
|
+ }
|
|
|
+ technology = strings.Trim(strings.Join(techParts, " "), "\"")
|
|
|
+ }
|
|
|
+
|
|
|
+ if !p.check(lexer.TokenCloseParen) {
|
|
|
+ return p.error("expected ')' to close SystemDb")
|
|
|
+ }
|
|
|
+ p.advance() // consume ')'
|
|
|
+
|
|
|
+ element := &ast.C4Element{
|
|
|
+ ID: dbID,
|
|
|
+ Type: ast.C4ElementDatabase,
|
|
|
+ Name: name,
|
|
|
+ Description: description,
|
|
|
+ }
|
|
|
+
|
|
|
+ if technology != "" {
|
|
|
+ element.Technology = &technology
|
|
|
+ }
|
|
|
+
|
|
|
+ p.diagram.AddElement(element)
|
|
|
+ return nil
|
|
|
+}
|
|
|
+
|
|
|
+// parseContainerDb parses ContainerDb statements
|
|
|
+func (p *C4Parser) parseContainerDb() error {
|
|
|
+ return p.parseSystemDb() // Similar implementation
|
|
|
+}
|
|
|
+
|
|
|
+// parseContainerQueue parses ContainerQueue statements
|
|
|
+func (p *C4Parser) parseContainerQueue() error {
|
|
|
+ // Similar to parseSystemDb but for queues
|
|
|
+ return p.parseSystemDb() // Simplified for now
|
|
|
+}
|
|
|
+
|
|
|
+// parseRelation parses relation statements
|
|
|
+func (p *C4Parser) parseRelation() error {
|
|
|
+ fromID := p.advance().Value
|
|
|
+
|
|
|
+ // Check for relation type
|
|
|
+ relationType := ast.C4RelationSync
|
|
|
+ if p.check(lexer.TokenArrowSolid) {
|
|
|
+ p.advance() // consume '-->'
|
|
|
+ } else if p.check(lexer.TokenMinus) {
|
|
|
+ p.advance() // consume first '-'
|
|
|
+ if p.check(lexer.TokenMinus) {
|
|
|
+ p.advance() // consume second '-'
|
|
|
+ if p.check(lexer.TokenCloseAngle) {
|
|
|
+ p.advance() // consume '>'
|
|
|
+ }
|
|
|
+ } else if p.check(lexer.TokenCloseAngle) {
|
|
|
+ p.advance() // consume '>'
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ if !p.check(lexer.TokenID) {
|
|
|
+ return p.error("expected target ID for relation")
|
|
|
+ }
|
|
|
+ toID := p.advance().Value
|
|
|
+
|
|
|
+ // Parse optional label
|
|
|
+ var label *string
|
|
|
+ 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 {
|
|
|
+ labelText := strings.TrimSpace(strings.Join(labelParts, " "))
|
|
|
+ label = &labelText
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ relation := &ast.C4Relation{
|
|
|
+ From: fromID,
|
|
|
+ To: toID,
|
|
|
+ Type: relationType,
|
|
|
+ }
|
|
|
+
|
|
|
+ if label != nil {
|
|
|
+ relation.Label = label
|
|
|
}
|
|
|
|
|
|
p.diagram.AddRelation(relation)
|