constraint.go raw

   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