// Package causal provides define-use analysis for Go source statements // and a lattice constraint that enforces causal correctness. // // The CausalConstraint implements axiom.ContextualConstraint. It allows // the lattice to learn causal ordering structurally — statements that // reference identifiers defined by neighbors bond more readily than // statements with no causal links. // // The constraint is intentionally lenient: it requires at least one // causal link OR allows self-contained statements. Over multiple // solve-et-coagula cycles, dissolution removes weakly-held elements // and re-growth places them in better neighborhoods. package causal import ( "strings" "unicode" "git.mleku.dev/mleku/dendrite/pkg/axiom" ) // CausalConstraint implements axiom.ContextualConstraint for body-level // Go source elements. It checks that at least one identifier used by // the element is defined by an occupied neighbor. type CausalConstraint struct { ConstraintTag string // the element type this constraint accepts } func (c CausalConstraint) Tag() string { return c.ConstraintTag } func (c CausalConstraint) Admits(e axiom.Element) bool { return e.Type() == c.ConstraintTag } func (c CausalConstraint) AdmitsInContext(e axiom.Element, neighbors []axiom.Element) bool { if !c.Admits(e) { return false } val, ok := e.Value().(string) if !ok || val == "" { return true } _, source := ParseParentSource(val) uses := ExtractUses(source) if len(uses) == 0 { return true // no dependencies — bonds anywhere } // Collect all identifiers defined by occupied neighbors. defined := make(map[string]bool) for _, nb := range neighbors { if nb == nil { continue } nbVal, ok := nb.Value().(string) if !ok { continue } _, nbSrc := ParseParentSource(nbVal) for id := range ExtractDefines(nbSrc) { defined[id] = true } } // At least one prerequisite must be satisfied by a neighbor. for id := range uses { if defined[id] { return true } } // No neighbor defines any used identifier. Allow bonding anyway — // the element might depend on globals, function params, or package-level // vars. The emitter's DAG sort handles the rest. return true } // ParseParentSource splits "parent\x00source" from a body element's value. func ParseParentSource(val string) (parent, source string) { idx := strings.IndexByte(val, '\x00') if idx < 0 { return "", val } return val[:idx], val[idx+1:] } // ExtractDefines returns identifiers defined by a Go source statement. // Looks for := left-hand sides and var declarations. func ExtractDefines(src string) map[string]bool { defs := make(map[string]bool) // Short-assignment: "x := expr" or "x, y := expr" if idx := strings.Index(src, ":="); idx >= 0 { lhs := strings.TrimSpace(src[:idx]) for _, part := range strings.Split(lhs, ",") { id := strings.TrimSpace(part) if IsIdent(id) && id != "_" { defs[id] = true } } } // var declarations: "var x int" or "var x = expr" if strings.HasPrefix(src, "var ") { rest := src[4:] tokens := Tokenize(rest) if len(tokens) > 0 && IsIdent(tokens[0]) { defs[tokens[0]] = true } } return defs } // ExtractUses returns identifiers used (but not defined) by a Go source statement. func ExtractUses(src string) map[string]bool { defs := ExtractDefines(src) uses := make(map[string]bool) for _, tok := range Tokenize(src) { if IsIdent(tok) && !GoKeywords[tok] && !defs[tok] && !GoBuiltins[tok] { uses[tok] = true } } return uses } // Tokenize splits source text on non-identifier characters. func Tokenize(src string) []string { return strings.FieldsFunc(src, func(r rune) bool { return !unicode.IsLetter(r) && !unicode.IsDigit(r) && r != '_' }) } // IsIdent returns true if s is a valid Go identifier. func IsIdent(s string) bool { if s == "" { return false } for i, r := range s { if i == 0 && !unicode.IsLetter(r) && r != '_' { return false } if !unicode.IsLetter(r) && !unicode.IsDigit(r) && r != '_' { return false } } return true } // GoKeywords is the set of Go reserved keywords. var GoKeywords = map[string]bool{ "break": true, "case": true, "chan": true, "const": true, "continue": true, "default": true, "defer": true, "else": true, "fallthrough": true, "for": true, "func": true, "go": true, "goto": true, "if": true, "import": true, "interface": true, "map": true, "package": true, "range": true, "return": true, "select": true, "struct": true, "switch": true, "type": true, "var": true, } // BodyStmtTags are element types that represent function body statements. // Used by scope-aware constraints to identify elements that participate // in define-use analysis. var BodyStmtTags = map[string]bool{ "assign": true, "return": true, "if": true, "for": true, "switch": true, "select": true, "go": true, "send": true, "expr": true, "defer": true, "decl": true, "branch": true, "case": true, "comm": true, } // ScopeConstraint enforces declaration-before-use within a function scope. // Body elements that reference identifiers must have at least one neighbor // in the same function scope that defines those identifiers. This creates // lattice-level pressure toward correct variable scoping. // // Unlike CausalConstraint (which is lenient and allows unresolved uses), // ScopeConstraint rejects elements whose uses are entirely unsatisfied // when same-scope neighbors exist that could provide definitions. // // Self-contained statements (no uses, or uses only globals/builtins) // always pass. Statements in scopes with no body-statement neighbors // also pass (seed bonding). type ScopeConstraint struct { ConstraintTag string } func (c ScopeConstraint) Tag() string { return c.ConstraintTag } func (c ScopeConstraint) Admits(e axiom.Element) bool { return e.Type() == c.ConstraintTag } func (c ScopeConstraint) AdmitsInContext(e axiom.Element, neighbors []axiom.Element) bool { if !c.Admits(e) { return false } val, ok := e.Value().(string) if !ok || val == "" { return true } parent, source := ParseParentSource(val) uses := ExtractUses(source) if len(uses) == 0 { return true // self-contained — no dependencies } // Collect definitions from same-scope neighbors (same parent function). defined := make(map[string]bool) hasScopeNeighbor := false for _, nb := range neighbors { if nb == nil { continue } if !BodyStmtTags[nb.Type()] { continue } nbVal, ok := nb.Value().(string) if !ok { continue } nbParent, nbSrc := ParseParentSource(nbVal) if nbParent != parent { continue // different function scope } hasScopeNeighbor = true for id := range ExtractDefines(nbSrc) { defined[id] = true } } // No same-scope neighbors — allow seed bonding. if !hasScopeNeighbor { return true } // At least one used identifier must be defined by a scope neighbor. for id := range uses { if defined[id] { return true } } // None of the uses are satisfied by scope neighbors. // Still allow if the statement defines something (it may be a // root definition that others depend on). defs := ExtractDefines(source) if len(defs) > 0 { return true } // Pure consumer with no scope-local definitions nearby — reject. // This biases walkers toward neighborhoods that have the definitions // the statement needs. return false } // UniqueDefConstraint prevents duplicate variable declarations within a // function scope. If a body element defines an identifier that is already // defined by a same-parent neighbor, the bond is rejected. // // Go allows re-declaration with := when at least one variable is new, // but duplicate definitions are a common source of bugs. This constraint // creates evolutionary pressure against them — the lattice prefers // arrangements where each identifier is defined once per scope. type UniqueDefConstraint struct { ConstraintTag string } func (c UniqueDefConstraint) Tag() string { return c.ConstraintTag } func (c UniqueDefConstraint) Admits(e axiom.Element) bool { return e.Type() == c.ConstraintTag } func (c UniqueDefConstraint) AdmitsInContext(e axiom.Element, neighbors []axiom.Element) bool { if !c.Admits(e) { return false } val, ok := e.Value().(string) if !ok || val == "" { return true } parent, source := ParseParentSource(val) defs := ExtractDefines(source) if len(defs) == 0 { return true // no definitions — no conflict possible } // Check same-scope neighbors for conflicting definitions. for _, nb := range neighbors { if nb == nil { continue } if !BodyStmtTags[nb.Type()] { continue } nbVal, ok := nb.Value().(string) if !ok { continue } nbParent, nbSrc := ParseParentSource(nbVal) if nbParent != parent { continue // different function scope } nbDefs := ExtractDefines(nbSrc) for id := range defs { if nbDefs[id] { return false // duplicate definition in same scope } } } return true } // GoBuiltins is the set of Go predeclared identifiers. var GoBuiltins = map[string]bool{ "bool": true, "byte": true, "complex64": true, "complex128": true, "error": true, "float32": true, "float64": true, "int": true, "int8": true, "int16": true, "int32": true, "int64": true, "rune": true, "string": true, "uint": true, "uint8": true, "uint16": true, "uint32": true, "uint64": true, "uintptr": true, "true": true, "false": true, "iota": true, "nil": true, "append": true, "cap": true, "clear": true, "close": true, "complex": true, "copy": true, "delete": true, "imag": true, "len": true, "make": true, "max": true, "min": true, "new": true, "panic": true, "print": true, "println": true, "real": true, "recover": true, "any": true, }