### Install Yaegi Command-Line Executable Source: https://github.com/traefik/yaegi/blob/master/README.md Install the Yaegi command-line tool using 'go install'. Consider using 'rlwrap' for enhanced command-line editing and history. ```bash go install github.com/traefik/yaegi/cmd/yaegi@latest ``` -------------------------------- ### Go OnceFunc Example Source: https://github.com/traefik/yaegi/blob/master/stdlib/generic/go1_22_sync_oncefunc.go.txt Demonstrates how to use sync.OnceFunc to ensure a function is called only once. This is useful for one-time initialization. ```go package sync // OnceFunc returns a function that invokes f only once. The returned function // may be called concurrently. // // If f panics, the returned function will panic with the same value on every call. func OnceFunc(f func()) func() { var ( once Once valid bool p any ) // Construct the inner closure just once to reduce costs on the fast path. g := func() { defer func() { p = recover() if !valid { // Re-panic immediately so on the first call the user gets a // complete stack trace into f. panic(p) } }() f() f = nil // Do not keep f alive after invoking it. valid = true // Set only if f does not panic. } return func() { once.Do(g) if !valid { panic(p) } } } ``` -------------------------------- ### Install Yaegi Go Package Source: https://github.com/traefik/yaegi/blob/master/README.md Import the Yaegi interpreter package into your Go project. ```go import "github.com/traefik/yaegi/interp" ``` -------------------------------- ### CI Integration for Yaegi Installation Source: https://github.com/traefik/yaegi/blob/master/README.md Use this curl command to download and execute the Yaegi installation script in your CI environment. Specify the desired version. ```bash curl -sfL https://raw.githubusercontent.com/traefik/yaegi/master/install.sh | bash -s -- -b $GOPATH/bin v0.9.0 ``` -------------------------------- ### Dynamic Extension Framework with Yaegi Source: https://github.com/traefik/yaegi/blob/master/README.md This example demonstrates using Yaegi to interpret Go code dynamically within a compiled program. It shows how to evaluate source code, retrieve symbols, and cast them to their expected types. ```go package main import "github.com/traefik/yaegi/interp" const src = `package foo func Bar(s string) string { return s + "-Foo" }` func main() { i := interp.New(interp.Options{}) _, err := i.Eval(src) if err != nil { panic(err) } v, err := i.Eval("foo.Bar") if err != nil { panic(err) } bar := v.Interface().(func(string) string) r := bar("Kung") println(r) } ``` -------------------------------- ### Go OnceValues Example Source: https://github.com/traefik/yaegi/blob/master/stdlib/generic/go1_22_sync_oncefunc.go.txt Demonstrates how to use sync.OnceValues to ensure a function returning multiple values is called only once. The results are cached and returned on subsequent calls. ```go package sync // OnceValues returns a function that invokes f only once and returns the values // returned by f. The returned function may be called concurrently. // // If f panics, the returned function will panic with the same value on every call. func OnceValues[T1, T2 any](f func() (T1, T2)) func() (T1, T2) { var ( once Once valid bool p any r1 T1 r2 T2 ) g := func() { defer func() { p = recover() if !valid { panic(p) } }() r1, r2 = f() f = nil valid = true } return func() (T1, T2) { once.Do(g) if !valid { panic(p) } return r1, r2 } } ``` -------------------------------- ### Go OnceValue Example Source: https://github.com/traefik/yaegi/blob/master/stdlib/generic/go1_22_sync_oncefunc.go.txt Demonstrates how to use sync.OnceValue to ensure a function returning a value is called only once. The result is cached and returned on subsequent calls. ```go package sync // OnceValue returns a function that invokes f only once and returns the value // returned by f. The returned function may be called concurrently. // // If f panics, the returned function will panic with the same value on every call. func OnceValue[T any](f func() T) func() T { var ( once Once valid bool p any result T ) g := func() { defer func() { p = recover() if !valid { panic(p) } }() result = f() f = nil valid = true } return func() T { once.Do(g) if !valid { panic(p) } return result } } ``` -------------------------------- ### startIdx: Find Needle Start Index in Haystack Source: https://github.com/traefik/yaegi/blob/master/stdlib/generic/go1_22_slices_slices.go.txt Returns the starting index of a needle slice within a haystack slice. This function requires that the needle is entirely contained within the haystack's memory range. It panics if the needle is not found. ```go func startIdx[E any](haystack, needle []E) int { p := &needle[0] for i := range haystack { if p == &haystack[i] { return i } } // TODO: what if the overlap is by a non-integral number of Es? panic("needle not found") } ``` -------------------------------- ### Swap Range for Ordered Slices Source: https://github.com/traefik/yaegi/blob/master/stdlib/generic/go1_21_slices.go.txt Swaps n elements starting from index a with n elements starting from index b in an ordered slice. ```go func swapRangeOrdered[E cmp.Ordered](data []E, a, b, n int) { for i := 0; i < n; i++ { data[a+i], data[b+i] = data[b+i], data[a+i] } } ``` -------------------------------- ### Find Start Index of Slice Sub-slice in Go Source: https://github.com/traefik/yaegi/blob/master/stdlib/generic/go1_21_slices.go.txt Returns the starting index of a 'needle' slice within a 'haystack' slice. Assumes the needle is entirely contained within the haystack's memory. ```go func startIdx[E any](haystack, needle []E) int { p := &needle[0] for i := range haystack { if p == &haystack[i] { return i } } panic("needle not found") } ``` -------------------------------- ### Compare Source: https://github.com/traefik/yaegi/blob/master/stdlib/generic/go1_22_slices_slices.go.txt Compare compares the elements of s1 and s2, using [cmp.Compare] on each pair of elements. The elements are compared sequentially, starting at index 0, until one element is not equal to the other. The result of comparing the first non-matching elements is returned. If both slices are equal until one of them ends, the shorter slice is considered less than the longer one. The result is 0 if s1 == s2, -1 if s1 < s2, and +1 if s1 > s2. ```APIDOC ## Compare[S ~[]E, E cmp.Ordered](s1, s2 S) int ### Description Compares the elements of s1 and s2, using [cmp.Compare] on each pair of elements. The elements are compared sequentially, starting at index 0, until one element is not equal to the other. The result of comparing the first non-matching elements is returned. If both slices are equal until one of them ends, the shorter slice is considered less than the longer one. The result is 0 if s1 == s2, -1 if s1 < s2, and +1 if s1 > s2. ### Parameters - **s1** (S) - The first slice. - **s2** (S) - The second slice. ### Returns - **int** - 0 if s1 == s2, -1 if s1 < s2, and +1 if s1 > s2. ``` -------------------------------- ### Swap Slice Segments Source: https://github.com/traefik/yaegi/blob/master/stdlib/generic/go1_22_slices_zsortanyfunc.go.txt Swaps 'n' elements starting from index 'a' with 'n' elements starting from index 'b' within a slice. ```go func swapRangeCmpFunc[E any](data []E, a, b, n int, cmp func(a, b E) int) { for i := 0; i < n; i++ { data[a+i], data[b+i] = data[b+i], data[a+i] } } ``` -------------------------------- ### CLI Interpreter with Pre-imported Packages Source: https://github.com/traefik/yaegi/blob/master/README.md Demonstrates using the Yaegi CLI interpreter with pre-imported packages like 'reflect' and 'time'. ```console $ yaegi > reflect.TypeOf(time.Date) : func(int, time.Month, int, int, int, int, int, *time.Location) time.Time > ``` -------------------------------- ### Choose Pivot for Quicksort Source: https://github.com/traefik/yaegi/blob/master/stdlib/generic/go1_22_slices_zsortanyfunc.go.txt Chooses a pivot element from a slice segment using different strategies based on the segment length: static pivot for small segments, median-of-three for medium, and Tukey ninther for larger segments. ```go // choosePivotCmpFunc chooses a pivot in data[a:b]. // // [0,8): chooses a static pivot. // [8,shortestNinther): uses the simple median-of-three method. // [shortestNinther,∞): uses the Tukey ninther method. func choosePivotCmpFunc[E any](data []E, a, b int, cmp func(a, b E) int) (pivot int, hint sortedHint) { const ( shortestNinther = 50 maxSwaps = 4 * 3 ) l := b - a var ( swaps int i = a + l/4*1 j = a + l/4*2 k = a + l/4*3 ) if l >= 8 { if l >= shortestNinther { // Tukey ninther method, the idea came from Rust's implementation. i = medianAdjacentCmpFunc(data, i, &swaps, cmp) j = medianAdjacentCmpFunc(data, j, &swaps, cmp) k = medianAdjacentCmpFunc(data, k, &swaps, cmp) } // Find the median among i, j, k and stores it into j. j = medianCmpFunc(data, i, j, k, &swaps, cmp) } switch swaps { case 0: return j, increasingHint case maxSwaps: return j, decreasingHint default: return j, unknownHint } } ``` -------------------------------- ### Partition for Generic Slices Source: https://github.com/traefik/yaegi/blob/master/stdlib/generic/go1_22_slices_zsortanyfunc.go.txt Performs a single quicksort partition step on a generic slice. Elements less than the pivot are moved to the left, and elements greater than or equal to the pivot are moved to the right. ```go func partitionCmpFunc[E any](data []E, a, b, pivot int, cmp func(a, b E) int) (newpivot int, alreadyPartitioned bool) { data[a], data[pivot] = data[pivot], data[a] i, j := a+1, b-1 // i and j are inclusive of the elements remaining to be partitioned for i <= j && (cmp(data[i], data[a]) < 0) { i++ } for i <= j && !(cmp(data[j], data[a]) < 0) { j-- } if i > j { data[j], data[a] = data[a], data[j] return j, true } data[i], data[j] = data[j], data[i] i++ j-- for { for i <= j && (cmp(data[i], data[a]) < 0) { i++ } for i <= j && !(cmp(data[j], data[a]) < 0) { j-- } if i > j { break } data[i], data[j] = data[j], data[i] i++ j-- } data[j], data[a] = data[a], data[j] return j, false } ``` -------------------------------- ### Interpreting Go Packages via CLI Source: https://github.com/traefik/yaegi/blob/master/README.md Execute Go packages, directories, or files using the Yaegi CLI with specific flags like -syscall, -unsafe, and -unrestricted. ```console $ yaegi -syscall -unsafe -unrestricted github.com/traefik/yaegi/cmd/yaegi > ``` -------------------------------- ### Partition Slice Elements (QuickSort) Source: https://github.com/traefik/yaegi/blob/master/stdlib/generic/go1_21_slices.go.txt Partitions a slice segment `data[a:b]` around a pivot element `data[a]`. It rearranges elements such that elements less than or equal to the pivot come before elements greater than the pivot. Assumes elements smaller than the pivot are not present in `data[a:b]`. ```go func partitionCmpFunc[E any](data []E, a, b, pivot int, cmp func(a, b E) int) (newpivot int) { data[a], data[pivot] = data[pivot], data[a] i, j := a+1, b-1 // i and j are inclusive of the elements remaining to be partitioned for { for i <= j && (cmp(data[a], data[i]) < 0) { i++ } for i <= j && !(cmp(data[a], data[j]) < 0) { j-- } if i > j { break } data[i], data[j] = data[j], data[i] i++ j-- } data[j], data[a] = data[a], data[j] return j, false } ``` -------------------------------- ### Go sync.Once.Do: Method to execute a function once Source: https://github.com/traefik/yaegi/blob/master/stdlib/generic/go1_21_sync.go.txt The `Do` method on `sync.Once` calls the provided function `f` if and only if `Do` has not been called before for this `Once` instance. It guarantees that `f` completes before `Do` returns. If `f` panics, `Do` considers it finished, and subsequent calls to `Do` will not execute `f`. ```go // Do calls the function f if and only if Do is being called for the // first time for this instance of Once. In other words, given // // var once Once // // if once.Do(f) is called multiple times, only the first call will invoke f, // even if f has a different value in each invocation. A new instance of // Once is required for each function to execute. // // Do is intended for initialization that must be run exactly once. Since f // is niladic, it may be necessary to use a function literal to capture the // arguments to a function to be invoked by Do: // // config.once.Do(func() { config.init(filename) }) // // Because no call to Do returns until the one call to f returns, if f causes // Do to be called, it will deadlock. // // If f panics, Do considers it to have returned; future calls of Do return // without calling f. func (o *Once) Do(f func()) { // Note: Here is an incorrect implementation of Do: // // if atomic.CompareAndSwapUint32(&o.done, 0, 1) { // f() // } // // Do guarantees that when it returns, f has finished. // This implementation would not implement that guarantee: // given two simultaneous calls, the winner of the cas would // call f, and the second would return immediately, without // waiting for the first's call to f to complete. // This is why the slow path falls back to a mutex, and why // the atomic.StoreUint32 must be delayed until after f returns. if atomic.LoadUint32(&o.done) == 0 { // Outlined slow-path to allow inlining of the fast-path. o.doSlow(f) } } ``` -------------------------------- ### PDQsort Implementation Source: https://github.com/traefik/yaegi/blob/master/stdlib/generic/go1_21_slices.go.txt Sorts a slice using a pattern-defeating quicksort algorithm. It falls back to heapsort if too many unbalanced partitions occur. This implementation is based on C++ and Rust versions. ```go // pdqsortCmpFunc sorts data[a:b]. // The algorithm based on pattern-defeating quicksort(pdqsort), but without the optimizations from BlockQuicksort. // pdqsort paper: https://arxiv.org/pdf/2106.05123.pdf // C++ implementation: https://github.com/orlp/pdqsort // Rust implementation: https://docs.rs/pdqsort/latest/pdqsort/ // limit is the number of allowed bad (very unbalanced) pivots before falling back to heapsort. func pdqsortCmpFunc[E any](data []E, a, b, limit int, cmp func(a, b E) int) { const maxInsertion = 12 var ( wasBalanced = true // whether the last partitioning was reasonably balanced wasPartitioned = true // whether the slice was already partitioned ) for { length := b - a if length <= maxInsertion { insertionSortCmpFunc(data, a, b, cmp) return } // Fall back to heapsort if too many bad choices were made. if limit == 0 { heapSortCmpFunc(data, a, b, cmp) return } // If the last partitioning was imbalanced, we need to breaking patterns. if !wasBalanced { breakPatternsCmpFunc(data, a, b, cmp) limit-- } pivot, hint := choosePivotCmpFunc(data, a, b, cmp) if hint == decreasingHint { reverseRangeCmpFunc(data, a, b, cmp) // The chosen pivot was pivot-a elements after the start of the array. // After reversing it is pivot-a elements before the end of the array. // The idea came from Rust's implementation. pivot = (b - 1) - (pivot - a) hint = increasingHint } // The slice is likely already sorted. if wasBalanced && wasPartitioned && hint == increasingHint { if partialInsertionSortCmpFunc(data, a, b, cmp) { return } } // Probably the slice contains many duplicate elements, partition the slice into // elements equal to and elements greater than the pivot. if a > 0 && !(cmp(data[a-1], data[pivot]) < 0) { mid := partitionEqualCmpFunc(data, a, b, pivot, cmp) a = mid continue } mid, alreadyPartitioned := partitionCmpFunc(data, a, b, pivot, cmp) wasPartitioned = alreadyPartitioned leftLen, rightLen := mid-a, b-mid balanceThreshold := length / 8 if leftLen < rightLen { wasBalanced = leftLen >= balanceThreshold pdqsortCmpFunc(data, a, mid, limit, cmp) a = mid + 1 } else { wasBalanced = rightLen >= balanceThreshold pdqsortCmpFunc(data, mid+1, b, limit, cmp) b = mid } } } ``` -------------------------------- ### PDQsort for Generic Slices Source: https://github.com/traefik/yaegi/blob/master/stdlib/generic/go1_22_slices_zsortanyfunc.go.txt Sorts a generic slice using a hybrid algorithm based on pattern-defeating quicksort. Falls back to heapsort for performance on pathological cases. ```go func pdqsortCmpFunc[E any](data []E, a, b, limit int, cmp func(a, b E) int) { const maxInsertion = 12 var ( wasBalanced = true // whether the last partitioning was reasonably balanced wasPartitioned = true // whether the slice was already partitioned ) for { length := b - a if length <= maxInsertion { insertionSortCmpFunc(data, a, b, cmp) return } // Fall back to heapsort if too many bad choices were made. if limit == 0 { heapSortCmpFunc(data, a, b, cmp) return } // If the last partitioning was imbalanced, we need to breaking patterns. if !wasBalanced { breakPatternsCmpFunc(data, a, b, cmp) limit-- } pivot, hint := choosePivotCmpFunc(data, a, b, cmp) if hint == decreasingHint { reverseRangeCmpFunc(data, a, b, cmp) // The chosen pivot was pivot-a elements after the start of the array. // After reversing it is pivot-a elements before the end of the array. // The idea came from Rust's implementation. pivot = (b - 1) - (pivot - a) hint = increasingHint } // The slice is likely already sorted. if wasBalanced && wasPartitioned && hint == increasingHint { if partialInsertionSortCmpFunc(data, a, b, cmp) { return } } // Probably the slice contains many duplicate elements, partition the slice into // elements equal to and elements greater than the pivot. if a > 0 && !(cmp(data[a-1], data[pivot]) < 0) { mid := partitionEqualCmpFunc(data, a, b, pivot, cmp) a = mid continue } mid, alreadyPartitioned := partitionCmpFunc(data, a, b, pivot, cmp) wasPartitioned = alreadyPartitioned leftLen, rightLen := mid-a, b-mid balanceThreshold := length / 8 if leftLen < rightLen { wasBalanced = leftLen >= balanceThreshold pdqsortCmpFunc(data, a, mid, limit, cmp) a = mid + 1 } else { wasBalanced = rightLen >= balanceThreshold pdqsortCmpFunc(data, mid+1, b, limit, cmp) b = mid } } } ``` -------------------------------- ### Go sync.Once: Ensure an action is performed exactly once Source: https://github.com/traefik/yaegi/blob/master/stdlib/generic/go1_21_sync.go.txt The `sync.Once` type is used to ensure that a particular action or initialization is performed exactly one time, even when called concurrently. It is crucial for safe initialization of shared resources. ```go // Once is an object that will perform exactly one action. // // A Once must not be copied after first use. // // In the terminology of the Go memory model, // the return from f “synchronizes before” // the return from any call of once.Do(f). type Once struct { // done indicates whether the action has been performed. // It is first in the struct because it is used in the hot path. // The hot path is inlined at every call site. // Placing done first allows more compact instructions on some architectures (amd64/386), // and fewer instructions (to calculate offset) on other architectures. done uint32 m Mutex } ``` -------------------------------- ### Quicksort Partition Function Source: https://github.com/traefik/yaegi/blob/master/stdlib/generic/go1_21_slices.go.txt Performs a single partition step for the quicksort algorithm. It rearranges elements around a pivot such that elements less than the pivot are to its left and elements greater than or equal to the pivot are to its right. ```go // partitionCmpFunc does one quicksort partition. // Let p = data[pivot] // Moves elements in data[a:b] around, so that data[i]
=p for i