github.com/aclements/go-misc@v0.0.0-20240129233631-2f6ede80790c/go-weave/amb/det.go (about)

     1  // Copyright 2016 The Go Authors. All rights reserved.
     2  // Use of this source code is governed by a BSD-style
     3  // license that can be found in the LICENSE file.
     4  
     5  package amb
     6  
     7  import "fmt"
     8  
     9  // StrategyDFS explores the ambiguous value space in depth-first order
    10  // up to MaxDepth. It is deterministic and (given enough time) will
    11  // explore the entire space.
    12  type StrategyDFS struct {
    13  	// MaxDepth specifies the maximum depth of the tree. If this
    14  	// is 0, it defaults to DefaultMaxDepth.
    15  	MaxDepth int
    16  
    17  	branchWidths []int
    18  	curPath      []int
    19  	step         int
    20  }
    21  
    22  func (s *StrategyDFS) Reset() {
    23  	s.branchWidths = nil
    24  	s.curPath = nil
    25  	s.step = 0
    26  }
    27  
    28  func (s *StrategyDFS) maxDepth() int {
    29  	if s.MaxDepth == 0 {
    30  		return DefaultMaxDepth
    31  	}
    32  	return s.MaxDepth
    33  }
    34  
    35  func (s *StrategyDFS) Amb(n int) (int, bool) {
    36  	if s.step < len(s.curPath) {
    37  		// We're in replay mode.
    38  		if n != s.branchWidths[s.step] {
    39  			panic(&ErrNondeterminism{fmt.Sprintf("Amb(%d) during replay, but previous call was Amb(%d)", n, s.branchWidths[s.step])})
    40  		}
    41  		res := s.curPath[s.step]
    42  		s.step++
    43  		return res, true
    44  	}
    45  
    46  	if len(s.curPath) == s.maxDepth() {
    47  		return 0, false
    48  	}
    49  
    50  	// We're in exploration mode.
    51  	s.branchWidths = append(s.branchWidths, n)
    52  	s.curPath = append(s.curPath, 0)
    53  	s.step++
    54  	return 0, true
    55  }
    56  
    57  func (s *StrategyDFS) Next() bool {
    58  	s.step = 0
    59  
    60  	// Construct the next path prefix to explore.
    61  	for i := len(s.curPath) - 1; i >= 0; i-- {
    62  		s.curPath[i]++
    63  		if s.curPath[i] < s.branchWidths[i] {
    64  			break
    65  		}
    66  		s.curPath = s.curPath[:len(s.curPath)-1]
    67  	}
    68  	s.branchWidths = s.branchWidths[:len(s.curPath)]
    69  	if len(s.branchWidths) == 0 {
    70  		// We're out of paths.
    71  		return false
    72  	}
    73  	return true
    74  }
    75  
    76  // ErrNondeterminism is the error used by deterministic strategies to
    77  // indicate that the strategy detected that the application behaved
    78  // non-deterministically.
    79  type ErrNondeterminism struct {
    80  	Detail string
    81  }
    82  
    83  func (e *ErrNondeterminism) Error() string {
    84  	return "non-determinism detected: " + e.Detail
    85  }