1 package grammar
2 3 import "git.mleku.dev/mleku/dendrite/pkg/axiom"
4 5 // GrammarConstraint wraps a tag constraint with grammar adjacency checking.
6 // During bonding, it verifies that the element's type is a valid grammar
7 // neighbor of the types already present in the node's neighborhood.
8 //
9 // Implements axiom.ContextualConstraint. The lattice Bond() method
10 // dispatches to AdmitsInContext when this interface is satisfied.
11 type GrammarConstraint struct {
12 tag string
13 grammar *Grammar
14 }
15 16 // NewConstraint creates a GrammarConstraint for the given tag and grammar.
17 func NewConstraint(tag string, g *Grammar) GrammarConstraint {
18 return GrammarConstraint{tag: tag, grammar: g}
19 }
20 21 // Tag returns the constraint's type tag.
22 func (c GrammarConstraint) Tag() string { return c.tag }
23 24 // Admits checks whether the element's type matches this constraint's tag.
25 func (c GrammarConstraint) Admits(e axiom.Element) bool {
26 return e.Type() == c.tag
27 }
28 29 // AdmitsInContext checks grammar adjacency: the element must have at least
30 // one occupied neighbor whose type is grammar-adjacent to the element's type.
31 // If no neighbors are occupied (seed bonding), the element is admitted.
32 func (c GrammarConstraint) AdmitsInContext(e axiom.Element, neighbors []axiom.Element) bool {
33 if !c.Admits(e) {
34 return false
35 }
36 37 // If no occupied neighbors, allow seed bonding.
38 hasOccupied := false
39 for _, nb := range neighbors {
40 if nb != nil {
41 hasOccupied = true
42 break
43 }
44 }
45 if !hasOccupied {
46 return true
47 }
48 49 // At least one occupied neighbor must have a grammar-adjacent type.
50 // Check bidirectionally: a→b OR b→a.
51 eType := e.Type()
52 for _, nb := range neighbors {
53 if nb == nil {
54 continue
55 }
56 nbType := nb.Type()
57 if c.grammar.CanNeighbor(eType, nbType) || c.grammar.CanNeighbor(nbType, eType) {
58 return true
59 }
60 }
61 return false
62 }
63