Merge pull request #27 from nektos/parser
Replace parser with new parser from GitHub
This commit is contained in:
commit
18427728ae
189 changed files with 26276 additions and 4015 deletions
2
.github/actions/check/entrypoint.sh
vendored
2
.github/actions/check/entrypoint.sh
vendored
|
@ -1,4 +1,4 @@
|
|||
#!/bin/sh
|
||||
set -e
|
||||
golangci-lint run
|
||||
go test -cover ./...
|
||||
go test -cover -short ./...
|
2
Makefile
2
Makefile
|
@ -50,6 +50,6 @@ endif
|
|||
git push origin $(NEW_VERSION)
|
||||
|
||||
vendor:
|
||||
go run main.go -ra vendor
|
||||
go mod vendor
|
||||
|
||||
.PHONY: vendor
|
|
@ -2,81 +2,46 @@ package actions
|
|||
|
||||
import (
|
||||
"fmt"
|
||||
"net/http"
|
||||
"net/url"
|
||||
"log"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"regexp"
|
||||
"strings"
|
||||
|
||||
"github.com/nektos/act/common"
|
||||
log "github.com/sirupsen/logrus"
|
||||
"github.com/actions/workflow-parser/model"
|
||||
"github.com/howeyc/gopass"
|
||||
)
|
||||
|
||||
// imageURL is the directory where a `Dockerfile` should exist
|
||||
func parseImageLocal(workingDir string, contextDir string) (contextDirOut string, tag string, ok bool) {
|
||||
if !strings.HasPrefix(contextDir, "./") {
|
||||
return "", "", false
|
||||
}
|
||||
contextDir = filepath.Join(workingDir, contextDir)
|
||||
if _, err := os.Stat(filepath.Join(contextDir, "Dockerfile")); os.IsNotExist(err) {
|
||||
log.Debugf("Ignoring missing Dockerfile '%s/Dockerfile'", contextDir)
|
||||
return "", "", false
|
||||
}
|
||||
var secretCache map[string]string
|
||||
|
||||
sha, _, err := common.FindGitRevision(contextDir)
|
||||
if err != nil {
|
||||
log.Warnf("Unable to determine git revision: %v", err)
|
||||
sha = "latest"
|
||||
}
|
||||
return contextDir, fmt.Sprintf("%s:%s", filepath.Base(contextDir), sha), true
|
||||
type actionEnvironmentApplier struct {
|
||||
*model.Action
|
||||
}
|
||||
|
||||
// imageURL is the URL for a docker repo
|
||||
func parseImageReference(image string) (ref string, ok bool) {
|
||||
imageURL, err := url.Parse(image)
|
||||
if err != nil {
|
||||
log.Debugf("Unable to parse image as url: %v", err)
|
||||
return "", false
|
||||
}
|
||||
if imageURL.Scheme != "docker" {
|
||||
log.Debugf("Ignoring non-docker ref '%s'", imageURL.String())
|
||||
return "", false
|
||||
}
|
||||
|
||||
return fmt.Sprintf("%s%s", imageURL.Host, imageURL.Path), true
|
||||
func newActionEnvironmentApplier(action *model.Action) environmentApplier {
|
||||
return &actionEnvironmentApplier{action}
|
||||
}
|
||||
|
||||
// imageURL is the directory where a `Dockerfile` should exist
|
||||
func parseImageGithub(image string) (cloneURL *url.URL, ref string, path string, ok bool) {
|
||||
re := regexp.MustCompile("^([^/@]+)/([^/@]+)(/([^@]*))?(@(.*))?$")
|
||||
matches := re.FindStringSubmatch(image)
|
||||
|
||||
if matches == nil {
|
||||
return nil, "", "", false
|
||||
func (action *actionEnvironmentApplier) applyEnvironment(env map[string]string) {
|
||||
for envKey, envValue := range action.Env {
|
||||
env[envKey] = envValue
|
||||
}
|
||||
|
||||
cloneURL, err := url.Parse(fmt.Sprintf("https://github.com/%s/%s", matches[1], matches[2]))
|
||||
if err != nil {
|
||||
log.Debugf("Unable to parse as URL: %v", err)
|
||||
return nil, "", "", false
|
||||
}
|
||||
for _, secret := range action.Secrets {
|
||||
if secretVal, ok := os.LookupEnv(secret); ok {
|
||||
env[secret] = secretVal
|
||||
} else {
|
||||
if secretCache == nil {
|
||||
secretCache = make(map[string]string)
|
||||
}
|
||||
|
||||
resp, err := http.Head(cloneURL.String())
|
||||
if resp.StatusCode >= 400 || err != nil {
|
||||
log.Debugf("Unable to HEAD URL %s status=%v err=%v", cloneURL.String(), resp.StatusCode, err)
|
||||
return nil, "", "", false
|
||||
}
|
||||
if _, ok := secretCache[secret]; !ok {
|
||||
fmt.Printf("Provide value for '%s': ", secret)
|
||||
val, err := gopass.GetPasswdMasked()
|
||||
if err != nil {
|
||||
log.Fatal("abort")
|
||||
}
|
||||
|
||||
ref = matches[6]
|
||||
if ref == "" {
|
||||
ref = "master"
|
||||
secretCache[secret] = string(val)
|
||||
}
|
||||
env[secret] = secretCache[secret]
|
||||
}
|
||||
}
|
||||
|
||||
path = matches[4]
|
||||
if path == "" {
|
||||
path = "."
|
||||
}
|
||||
|
||||
return cloneURL, ref, path, true
|
||||
}
|
||||
|
|
64
actions/graph.go
Normal file
64
actions/graph.go
Normal file
|
@ -0,0 +1,64 @@
|
|||
package actions
|
||||
|
||||
import (
|
||||
"log"
|
||||
|
||||
"github.com/actions/workflow-parser/model"
|
||||
)
|
||||
|
||||
// return a pipeline that is run in series. pipeline is a list of steps to run in parallel
|
||||
func newExecutionGraph(workflowConfig *model.Configuration, actionNames ...string) [][]string {
|
||||
// first, build a list of all the necessary actions to run, and their dependencies
|
||||
actionDependencies := make(map[string][]string)
|
||||
for len(actionNames) > 0 {
|
||||
newActionNames := make([]string, 0)
|
||||
for _, aName := range actionNames {
|
||||
// make sure we haven't visited this action yet
|
||||
if _, ok := actionDependencies[aName]; !ok {
|
||||
action := workflowConfig.GetAction(aName)
|
||||
if action != nil {
|
||||
actionDependencies[aName] = action.Needs
|
||||
newActionNames = append(newActionNames, action.Needs...)
|
||||
}
|
||||
}
|
||||
}
|
||||
actionNames = newActionNames
|
||||
}
|
||||
|
||||
// next, build an execution graph
|
||||
graph := make([][]string, 0)
|
||||
for len(actionDependencies) > 0 {
|
||||
stage := make([]string, 0)
|
||||
for aName, aDeps := range actionDependencies {
|
||||
// make sure all deps are in the graph already
|
||||
if listInLists(aDeps, graph...) {
|
||||
stage = append(stage, aName)
|
||||
delete(actionDependencies, aName)
|
||||
}
|
||||
}
|
||||
if len(stage) == 0 {
|
||||
log.Fatalf("Unable to build dependency graph!")
|
||||
}
|
||||
graph = append(graph, stage)
|
||||
}
|
||||
|
||||
return graph
|
||||
}
|
||||
|
||||
// return true iff all strings in srcList exist in at least one of the searchLists
|
||||
func listInLists(srcList []string, searchLists ...[]string) bool {
|
||||
for _, src := range srcList {
|
||||
found := false
|
||||
for _, searchList := range searchLists {
|
||||
for _, search := range searchList {
|
||||
if src == search {
|
||||
found = true
|
||||
}
|
||||
}
|
||||
}
|
||||
if !found {
|
||||
return false
|
||||
}
|
||||
}
|
||||
return true
|
||||
}
|
129
actions/model.go
129
actions/model.go
|
@ -1,129 +0,0 @@
|
|||
package actions
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"log"
|
||||
"os"
|
||||
|
||||
"github.com/howeyc/gopass"
|
||||
)
|
||||
|
||||
type workflowModel struct {
|
||||
On string
|
||||
Resolves []string
|
||||
}
|
||||
|
||||
type actionModel struct {
|
||||
Needs []string
|
||||
Uses string
|
||||
Runs []string
|
||||
Args []string
|
||||
Env map[string]string
|
||||
Secrets []string
|
||||
}
|
||||
|
||||
type workflowsFile struct {
|
||||
Workflow map[string]workflowModel
|
||||
Action map[string]actionModel
|
||||
}
|
||||
|
||||
func (wFile *workflowsFile) getWorkflow(eventName string) (*workflowModel, string, error) {
|
||||
var rtn workflowModel
|
||||
for wName, w := range wFile.Workflow {
|
||||
if w.On == eventName {
|
||||
rtn = w
|
||||
return &rtn, wName, nil
|
||||
}
|
||||
}
|
||||
return nil, "", fmt.Errorf("unsupported event: %v", eventName)
|
||||
}
|
||||
|
||||
func (wFile *workflowsFile) getAction(actionName string) (*actionModel, error) {
|
||||
if a, ok := wFile.Action[actionName]; ok {
|
||||
return &a, nil
|
||||
}
|
||||
return nil, fmt.Errorf("unsupported action: %v", actionName)
|
||||
}
|
||||
|
||||
// return a pipeline that is run in series. pipeline is a list of steps to run in parallel
|
||||
func (wFile *workflowsFile) newExecutionGraph(actionNames ...string) [][]string {
|
||||
// first, build a list of all the necessary actions to run, and their dependencies
|
||||
actionDependencies := make(map[string][]string)
|
||||
for len(actionNames) > 0 {
|
||||
newActionNames := make([]string, 0)
|
||||
for _, aName := range actionNames {
|
||||
// make sure we haven't visited this action yet
|
||||
if _, ok := actionDependencies[aName]; !ok {
|
||||
actionDependencies[aName] = wFile.Action[aName].Needs
|
||||
newActionNames = append(newActionNames, wFile.Action[aName].Needs...)
|
||||
}
|
||||
}
|
||||
actionNames = newActionNames
|
||||
}
|
||||
|
||||
// next, build an execution graph
|
||||
graph := make([][]string, 0)
|
||||
for len(actionDependencies) > 0 {
|
||||
stage := make([]string, 0)
|
||||
for aName, aDeps := range actionDependencies {
|
||||
// make sure all deps are in the graph already
|
||||
if listInLists(aDeps, graph...) {
|
||||
stage = append(stage, aName)
|
||||
delete(actionDependencies, aName)
|
||||
}
|
||||
}
|
||||
if len(stage) == 0 {
|
||||
log.Fatalf("Unable to build dependency graph!")
|
||||
}
|
||||
graph = append(graph, stage)
|
||||
}
|
||||
|
||||
return graph
|
||||
}
|
||||
|
||||
// return true iff all strings in srcList exist in at least one of the searchLists
|
||||
func listInLists(srcList []string, searchLists ...[]string) bool {
|
||||
for _, src := range srcList {
|
||||
found := false
|
||||
for _, searchList := range searchLists {
|
||||
for _, search := range searchList {
|
||||
if src == search {
|
||||
found = true
|
||||
}
|
||||
}
|
||||
}
|
||||
if !found {
|
||||
return false
|
||||
}
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
var secretCache map[string]string
|
||||
|
||||
func (action *actionModel) applyEnvironment(env map[string]string) {
|
||||
for envKey, envValue := range action.Env {
|
||||
env[envKey] = envValue
|
||||
}
|
||||
|
||||
for _, secret := range action.Secrets {
|
||||
if secretVal, ok := os.LookupEnv(secret); ok {
|
||||
env[secret] = secretVal
|
||||
} else {
|
||||
if secretCache == nil {
|
||||
secretCache = make(map[string]string)
|
||||
}
|
||||
|
||||
if _, ok := secretCache[secret]; !ok {
|
||||
fmt.Printf("Provide value for '%s': ", secret)
|
||||
val, err := gopass.GetPasswdMasked()
|
||||
if err != nil {
|
||||
log.Fatal("abort")
|
||||
}
|
||||
|
||||
secretCache[secret] = string(val)
|
||||
}
|
||||
env[secret] = secretCache[secret]
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,140 +0,0 @@
|
|||
package actions
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"errors"
|
||||
"fmt"
|
||||
"io"
|
||||
"strings"
|
||||
|
||||
"github.com/hashicorp/hcl"
|
||||
"github.com/hashicorp/hcl/hcl/ast"
|
||||
"github.com/hashicorp/hcl/hcl/token"
|
||||
log "github.com/sirupsen/logrus"
|
||||
)
|
||||
|
||||
func parseWorkflowsFile(workflowReader io.Reader) (*workflowsFile, error) {
|
||||
// TODO: add validation logic
|
||||
// - check for circular dependencies
|
||||
// - check for valid local path refs
|
||||
// - check for valid dependencies
|
||||
|
||||
buf := new(bytes.Buffer)
|
||||
_, err := buf.ReadFrom(workflowReader)
|
||||
if err != nil {
|
||||
log.Error(err)
|
||||
}
|
||||
|
||||
workflows := new(workflowsFile)
|
||||
|
||||
astFile, err := hcl.ParseBytes(buf.Bytes())
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
rootNode := ast.Walk(astFile.Node, cleanWorkflowsAST)
|
||||
err = hcl.DecodeObject(workflows, rootNode)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return workflows, nil
|
||||
}
|
||||
|
||||
func cleanWorkflowsAST(node ast.Node) (ast.Node, bool) {
|
||||
if objectItem, ok := node.(*ast.ObjectItem); ok {
|
||||
key := objectItem.Keys[0].Token.Value()
|
||||
|
||||
// handle condition where value is a string but should be a list
|
||||
switch key {
|
||||
case "args", "runs":
|
||||
if literalType, ok := objectItem.Val.(*ast.LiteralType); ok {
|
||||
listType := new(ast.ListType)
|
||||
parts, err := parseCommand(literalType.Token.Value().(string))
|
||||
if err != nil {
|
||||
return nil, false
|
||||
}
|
||||
quote := literalType.Token.Text[0]
|
||||
for _, part := range parts {
|
||||
part = fmt.Sprintf("%c%s%c", quote, strings.Replace(part, "\\", "\\\\", -1), quote)
|
||||
listType.Add(&ast.LiteralType{
|
||||
Token: token.Token{
|
||||
Type: token.STRING,
|
||||
Text: part,
|
||||
},
|
||||
})
|
||||
}
|
||||
objectItem.Val = listType
|
||||
|
||||
}
|
||||
case "resolves", "needs":
|
||||
if literalType, ok := objectItem.Val.(*ast.LiteralType); ok {
|
||||
listType := new(ast.ListType)
|
||||
listType.Add(literalType)
|
||||
objectItem.Val = listType
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
return node, true
|
||||
}
|
||||
|
||||
// reused from: https://github.com/laurent22/massren/blob/ae4c57da1e09a95d9383f7eb645a9f69790dec6c/main.go#L172
|
||||
// nolint: gocyclo
|
||||
func parseCommand(cmd string) ([]string, error) {
|
||||
var args []string
|
||||
state := "start"
|
||||
current := ""
|
||||
quote := "\""
|
||||
for i := 0; i < len(cmd); i++ {
|
||||
c := cmd[i]
|
||||
|
||||
if state == "quotes" {
|
||||
if string(c) != quote {
|
||||
current += string(c)
|
||||
} else {
|
||||
args = append(args, current)
|
||||
current = ""
|
||||
state = "start"
|
||||
}
|
||||
continue
|
||||
}
|
||||
|
||||
if c == '"' || c == '\'' {
|
||||
state = "quotes"
|
||||
quote = string(c)
|
||||
continue
|
||||
}
|
||||
|
||||
if state == "arg" {
|
||||
if c == ' ' || c == '\t' {
|
||||
args = append(args, current)
|
||||
current = ""
|
||||
state = "start"
|
||||
} else {
|
||||
current += string(c)
|
||||
}
|
||||
continue
|
||||
}
|
||||
|
||||
if c != ' ' && c != '\t' {
|
||||
state = "arg"
|
||||
current += string(c)
|
||||
}
|
||||
}
|
||||
|
||||
if state == "quotes" {
|
||||
return []string{}, fmt.Errorf("unclosed quote in command line: %s", cmd)
|
||||
}
|
||||
|
||||
if current != "" {
|
||||
args = append(args, current)
|
||||
}
|
||||
|
||||
if len(args) == 0 {
|
||||
return []string{}, errors.New("empty command line")
|
||||
}
|
||||
|
||||
log.Debugf("Parsed literal %+q to list %+q", cmd, args)
|
||||
|
||||
return args, nil
|
||||
}
|
|
@ -1,161 +0,0 @@
|
|||
package actions
|
||||
|
||||
import (
|
||||
"strings"
|
||||
"testing"
|
||||
|
||||
log "github.com/sirupsen/logrus"
|
||||
"github.com/stretchr/testify/assert"
|
||||
)
|
||||
|
||||
func TestParseWorkflowsFile(t *testing.T) {
|
||||
log.SetLevel(log.DebugLevel)
|
||||
|
||||
conf := `
|
||||
workflow "build-and-deploy" {
|
||||
on = "push"
|
||||
resolves = ["deploy"]
|
||||
}
|
||||
|
||||
action "build" {
|
||||
uses = "./action1"
|
||||
args = "echo 'build'"
|
||||
}
|
||||
|
||||
action "test" {
|
||||
uses = "docker://ubuntu:18.04"
|
||||
runs = "echo 'test'"
|
||||
needs = ["build"]
|
||||
}
|
||||
|
||||
action "deploy" {
|
||||
uses = "./action2"
|
||||
args = ["echo","deploy"]
|
||||
needs = ["test"]
|
||||
}
|
||||
|
||||
action "docker-login" {
|
||||
uses = "docker://docker"
|
||||
runs = ["sh", "-c", "echo $DOCKER_AUTH | docker login --username $REGISTRY_USER --password-stdin"]
|
||||
secrets = ["DOCKER_AUTH"]
|
||||
env = {
|
||||
REGISTRY_USER = "username"
|
||||
}
|
||||
}
|
||||
|
||||
action "unit-tests" {
|
||||
uses = "./scripts/github_actions"
|
||||
runs = "yarn test:ci-unittest || echo \"Unit tests failed, but running danger to present the results!\" 2>&1"
|
||||
}
|
||||
|
||||
action "regex-in-args" {
|
||||
uses = "actions/bin/filter@master"
|
||||
args = "tag v?[0-9]+\\.[0-9]+\\.[0-9]+"
|
||||
}
|
||||
|
||||
action "regex-in-args-array" {
|
||||
uses = "actions/bin/filter@master"
|
||||
args = ["tag","v?[0-9]+\\.[0-9]+\\.[0-9]+"]
|
||||
}
|
||||
`
|
||||
|
||||
workflows, err := parseWorkflowsFile(strings.NewReader(conf))
|
||||
|
||||
assert.Nil(t, err)
|
||||
assert.Equal(t, 1, len(workflows.Workflow))
|
||||
|
||||
w, wName, _ := workflows.getWorkflow("push")
|
||||
assert.Equal(t, "build-and-deploy", wName)
|
||||
assert.ElementsMatch(t, []string{"deploy"}, w.Resolves)
|
||||
|
||||
actions := []struct {
|
||||
name string
|
||||
uses string
|
||||
needs []string
|
||||
runs []string
|
||||
args []string
|
||||
secrets []string
|
||||
}{
|
||||
{"build",
|
||||
"./action1",
|
||||
nil,
|
||||
nil,
|
||||
[]string{"echo", "build"},
|
||||
nil,
|
||||
},
|
||||
{"test",
|
||||
"docker://ubuntu:18.04",
|
||||
[]string{"build"},
|
||||
[]string{"echo", "test"},
|
||||
nil,
|
||||
nil,
|
||||
},
|
||||
{"deploy",
|
||||
"./action2",
|
||||
[]string{"test"},
|
||||
nil,
|
||||
[]string{"echo", "deploy"},
|
||||
nil,
|
||||
},
|
||||
{"docker-login",
|
||||
"docker://docker",
|
||||
nil,
|
||||
[]string{"sh", "-c", "echo $DOCKER_AUTH | docker login --username $REGISTRY_USER --password-stdin"},
|
||||
nil,
|
||||
[]string{"DOCKER_AUTH"},
|
||||
},
|
||||
{"unit-tests",
|
||||
"./scripts/github_actions",
|
||||
nil,
|
||||
[]string{"yarn", "test:ci-unittest", "||", "echo", "Unit tests failed, but running danger to present the results!", "2>&1"},
|
||||
nil,
|
||||
nil,
|
||||
},
|
||||
{"regex-in-args",
|
||||
"actions/bin/filter@master",
|
||||
nil,
|
||||
nil,
|
||||
[]string{"tag", `v?[0-9]+\.[0-9]+\.[0-9]+`},
|
||||
nil,
|
||||
},
|
||||
{"regex-in-args-array",
|
||||
"actions/bin/filter@master",
|
||||
nil,
|
||||
nil,
|
||||
[]string{"tag", `v?[0-9]+\.[0-9]+\.[0-9]+`},
|
||||
nil,
|
||||
},
|
||||
}
|
||||
|
||||
for _, exp := range actions {
|
||||
act, _ := workflows.getAction(exp.name)
|
||||
assert.Equal(t, exp.uses, act.Uses, "[%s] Uses", exp.name)
|
||||
if exp.needs == nil {
|
||||
assert.Nil(t, act.Needs, "[%s] Needs", exp.name)
|
||||
} else {
|
||||
assert.ElementsMatch(t, exp.needs, act.Needs, "[%s] Needs", exp.name)
|
||||
}
|
||||
if exp.runs == nil {
|
||||
assert.Nil(t, act.Runs, "[%s] Runs", exp.name)
|
||||
} else {
|
||||
assert.ElementsMatch(t, exp.runs, act.Runs, "[%s] Runs", exp.name)
|
||||
}
|
||||
if exp.args == nil {
|
||||
assert.Nil(t, act.Args, "[%s] Args", exp.name)
|
||||
} else {
|
||||
assert.ElementsMatch(t, exp.args, act.Args, "[%s] Args", exp.name)
|
||||
}
|
||||
/*
|
||||
if exp.env == nil {
|
||||
assert.Nil(t, act.Env, "[%s] Env", exp.name)
|
||||
} else {
|
||||
assert.ElementsMatch(t, exp.env, act.Env, "[%s] Env", exp.name)
|
||||
}
|
||||
*/
|
||||
if exp.secrets == nil {
|
||||
assert.Nil(t, act.Secrets, "[%s] Secrets", exp.name)
|
||||
} else {
|
||||
assert.ElementsMatch(t, exp.secrets, act.Secrets, "[%s] Secrets", exp.name)
|
||||
}
|
||||
}
|
||||
}
|
|
@ -6,15 +6,17 @@ import (
|
|||
"path/filepath"
|
||||
"sort"
|
||||
|
||||
"github.com/actions/workflow-parser/model"
|
||||
"github.com/actions/workflow-parser/parser"
|
||||
"github.com/nektos/act/common"
|
||||
log "github.com/sirupsen/logrus"
|
||||
)
|
||||
|
||||
type runnerImpl struct {
|
||||
config *RunnerConfig
|
||||
workflows *workflowsFile
|
||||
tempDir string
|
||||
eventJSON string
|
||||
config *RunnerConfig
|
||||
workflowConfig *model.Configuration
|
||||
tempDir string
|
||||
eventJSON string
|
||||
}
|
||||
|
||||
// NewRunner Creates a new Runner
|
||||
|
@ -56,7 +58,15 @@ func (runner *runnerImpl) setupWorkflows() error {
|
|||
|
||||
defer workflowReader.Close()
|
||||
|
||||
runner.workflows, err = parseWorkflowsFile(workflowReader)
|
||||
runner.workflowConfig, err = parser.Parse(workflowReader)
|
||||
/*
|
||||
if err != nil {
|
||||
parserError := err.(*parser.ParserError)
|
||||
for _, e := range parserError.Errors {
|
||||
fmt.Fprintln(os.Stderr, e)
|
||||
}
|
||||
}
|
||||
*/
|
||||
return err
|
||||
}
|
||||
|
||||
|
@ -88,7 +98,7 @@ func (runner *runnerImpl) resolvePath(path string) string {
|
|||
func (runner *runnerImpl) ListEvents() []string {
|
||||
log.Debugf("Listing all events")
|
||||
events := make([]string, 0)
|
||||
for _, w := range runner.workflows.Workflow {
|
||||
for _, w := range runner.workflowConfig.Workflows {
|
||||
events = append(events, w.On)
|
||||
}
|
||||
|
||||
|
@ -103,17 +113,14 @@ func (runner *runnerImpl) ListEvents() []string {
|
|||
// GraphEvent builds an execution path
|
||||
func (runner *runnerImpl) GraphEvent(eventName string) ([][]string, error) {
|
||||
log.Debugf("Listing actions for event '%s'", eventName)
|
||||
workflow, _, err := runner.workflows.getWorkflow(eventName)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return runner.workflows.newExecutionGraph(workflow.Resolves...), nil
|
||||
resolves := runner.resolveEvent(runner.config.EventName)
|
||||
return newExecutionGraph(runner.workflowConfig, resolves...), nil
|
||||
}
|
||||
|
||||
// RunAction runs a set of actions in parallel, and their dependencies
|
||||
func (runner *runnerImpl) RunActions(actionNames ...string) error {
|
||||
log.Debugf("Running actions %+q", actionNames)
|
||||
graph := runner.workflows.newExecutionGraph(actionNames...)
|
||||
graph := newExecutionGraph(runner.workflowConfig, actionNames...)
|
||||
|
||||
pipeline := make([]common.Executor, 0)
|
||||
for _, actions := range graph {
|
||||
|
@ -131,15 +138,32 @@ func (runner *runnerImpl) RunActions(actionNames ...string) error {
|
|||
// RunEvent runs the actions for a single event
|
||||
func (runner *runnerImpl) RunEvent() error {
|
||||
log.Debugf("Running event '%s'", runner.config.EventName)
|
||||
workflow, _, err := runner.workflows.getWorkflow(runner.config.EventName)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
log.Debugf("Running actions %s -> %s", runner.config.EventName, workflow.Resolves)
|
||||
return runner.RunActions(workflow.Resolves...)
|
||||
resolves := runner.resolveEvent(runner.config.EventName)
|
||||
log.Debugf("Running actions %s -> %s", runner.config.EventName, resolves)
|
||||
return runner.RunActions(resolves...)
|
||||
}
|
||||
|
||||
func (runner *runnerImpl) Close() error {
|
||||
return os.RemoveAll(runner.tempDir)
|
||||
}
|
||||
|
||||
// get list of resolves for an event
|
||||
func (runner *runnerImpl) resolveEvent(eventName string) []string {
|
||||
workflows := runner.workflowConfig.GetWorkflows(runner.config.EventName)
|
||||
resolves := make([]string, 0)
|
||||
for _, workflow := range workflows {
|
||||
for _, resolve := range workflow.Resolves {
|
||||
found := false
|
||||
for _, r := range resolves {
|
||||
if r == resolve {
|
||||
found = true
|
||||
break
|
||||
}
|
||||
}
|
||||
if !found {
|
||||
resolves = append(resolves, resolve)
|
||||
}
|
||||
}
|
||||
}
|
||||
return resolves
|
||||
}
|
||||
|
|
|
@ -9,25 +9,96 @@ import (
|
|||
"path/filepath"
|
||||
"regexp"
|
||||
|
||||
"github.com/actions/workflow-parser/model"
|
||||
"github.com/nektos/act/common"
|
||||
"github.com/nektos/act/container"
|
||||
log "github.com/sirupsen/logrus"
|
||||
)
|
||||
|
||||
func (runner *runnerImpl) newActionExecutor(actionName string) common.Executor {
|
||||
action, err := runner.workflows.getAction(actionName)
|
||||
action := runner.workflowConfig.GetAction(actionName)
|
||||
if action == nil {
|
||||
return common.NewErrorExecutor(fmt.Errorf("Unable to find action named '%s'", actionName))
|
||||
}
|
||||
|
||||
executors := make([]common.Executor, 0)
|
||||
image, err := runner.addImageExecutor(action, &executors)
|
||||
if err != nil {
|
||||
return common.NewErrorExecutor(err)
|
||||
}
|
||||
|
||||
env := make(map[string]string)
|
||||
for _, applier := range []environmentApplier{action, runner} {
|
||||
applier.applyEnvironment(env)
|
||||
err = runner.addRunExecutor(action, image, &executors)
|
||||
if err != nil {
|
||||
return common.NewErrorExecutor(err)
|
||||
}
|
||||
env["GITHUB_ACTION"] = actionName
|
||||
|
||||
logger := newActionLogger(actionName, runner.config.Dryrun)
|
||||
log.Debugf("Using '%s' for action '%s'", action.Uses, actionName)
|
||||
return common.NewPipelineExecutor(executors...)
|
||||
}
|
||||
|
||||
func (runner *runnerImpl) addImageExecutor(action *model.Action, executors *[]common.Executor) (string, error) {
|
||||
var image string
|
||||
logger := newActionLogger(action.Identifier, runner.config.Dryrun)
|
||||
log.Debugf("Using '%s' for action '%s'", action.Uses, action.Identifier)
|
||||
|
||||
in := container.DockerExecutorInput{
|
||||
Ctx: runner.config.Ctx,
|
||||
Logger: logger,
|
||||
Dryrun: runner.config.Dryrun,
|
||||
}
|
||||
switch uses := action.Uses.(type) {
|
||||
|
||||
case *model.UsesDockerImage:
|
||||
image = uses.Image
|
||||
*executors = append(*executors, container.NewDockerPullExecutor(container.NewDockerPullExecutorInput{
|
||||
DockerExecutorInput: in,
|
||||
Image: image,
|
||||
}))
|
||||
|
||||
case *model.UsesPath:
|
||||
contextDir := filepath.Join(runner.config.WorkingDir, uses.String())
|
||||
sha, _, err := common.FindGitRevision(contextDir)
|
||||
if err != nil {
|
||||
log.Warnf("Unable to determine git revision: %v", err)
|
||||
sha = "latest"
|
||||
}
|
||||
image = fmt.Sprintf("%s:%s", filepath.Base(contextDir), sha)
|
||||
|
||||
*executors = append(*executors, container.NewDockerBuildExecutor(container.NewDockerBuildExecutorInput{
|
||||
DockerExecutorInput: in,
|
||||
ContextDir: contextDir,
|
||||
ImageTag: image,
|
||||
}))
|
||||
|
||||
case *model.UsesRepository:
|
||||
image = fmt.Sprintf("%s:%s", filepath.Base(uses.Repository), uses.Ref)
|
||||
cloneURL := fmt.Sprintf("https://github.com/%s", uses.Repository)
|
||||
|
||||
cloneDir := filepath.Join(os.TempDir(), "act", action.Uses.String())
|
||||
*executors = append(*executors, common.NewGitCloneExecutor(common.NewGitCloneExecutorInput{
|
||||
URL: cloneURL,
|
||||
Ref: uses.Ref,
|
||||
Dir: cloneDir,
|
||||
Logger: logger,
|
||||
Dryrun: runner.config.Dryrun,
|
||||
}))
|
||||
|
||||
contextDir := filepath.Join(cloneDir, uses.Path)
|
||||
*executors = append(*executors, container.NewDockerBuildExecutor(container.NewDockerBuildExecutorInput{
|
||||
DockerExecutorInput: in,
|
||||
ContextDir: contextDir,
|
||||
ImageTag: image,
|
||||
}))
|
||||
|
||||
default:
|
||||
return "", fmt.Errorf("unable to determine executor type for image '%s'", action.Uses)
|
||||
}
|
||||
|
||||
return image, nil
|
||||
}
|
||||
|
||||
func (runner *runnerImpl) addRunExecutor(action *model.Action, image string, executors *[]common.Executor) error {
|
||||
logger := newActionLogger(action.Identifier, runner.config.Dryrun)
|
||||
log.Debugf("Using '%s' for action '%s'", action.Uses, action.Identifier)
|
||||
|
||||
in := container.DockerExecutorInput{
|
||||
Ctx: runner.config.Ctx,
|
||||
|
@ -35,61 +106,37 @@ func (runner *runnerImpl) newActionExecutor(actionName string) common.Executor {
|
|||
Dryrun: runner.config.Dryrun,
|
||||
}
|
||||
|
||||
var image string
|
||||
executors := make([]common.Executor, 0)
|
||||
if imageRef, ok := parseImageReference(action.Uses); ok {
|
||||
executors = append(executors, container.NewDockerPullExecutor(container.NewDockerPullExecutorInput{
|
||||
DockerExecutorInput: in,
|
||||
Image: imageRef,
|
||||
}))
|
||||
image = imageRef
|
||||
} else if contextDir, imageTag, ok := parseImageLocal(runner.config.WorkingDir, action.Uses); ok {
|
||||
executors = append(executors, container.NewDockerBuildExecutor(container.NewDockerBuildExecutorInput{
|
||||
DockerExecutorInput: in,
|
||||
ContextDir: contextDir,
|
||||
ImageTag: imageTag,
|
||||
}))
|
||||
image = imageTag
|
||||
} else if cloneURL, ref, path, ok := parseImageGithub(action.Uses); ok {
|
||||
cloneDir := filepath.Join(os.TempDir(), "act", action.Uses)
|
||||
executors = append(executors, common.NewGitCloneExecutor(common.NewGitCloneExecutorInput{
|
||||
URL: cloneURL,
|
||||
Ref: ref,
|
||||
Dir: cloneDir,
|
||||
Logger: logger,
|
||||
Dryrun: runner.config.Dryrun,
|
||||
}))
|
||||
|
||||
contextDir := filepath.Join(cloneDir, path)
|
||||
imageTag := fmt.Sprintf("%s:%s", filepath.Base(cloneURL.Path), ref)
|
||||
|
||||
executors = append(executors, container.NewDockerBuildExecutor(container.NewDockerBuildExecutorInput{
|
||||
DockerExecutorInput: in,
|
||||
ContextDir: contextDir,
|
||||
ImageTag: imageTag,
|
||||
}))
|
||||
image = imageTag
|
||||
} else {
|
||||
return common.NewErrorExecutor(fmt.Errorf("unable to determine executor type for image '%s'", action.Uses))
|
||||
env := make(map[string]string)
|
||||
for _, applier := range []environmentApplier{newActionEnvironmentApplier(action), runner} {
|
||||
applier.applyEnvironment(env)
|
||||
}
|
||||
env["GITHUB_ACTION"] = action.Identifier
|
||||
|
||||
ghReader, err := runner.createGithubTarball()
|
||||
if err != nil {
|
||||
return common.NewErrorExecutor(err)
|
||||
return err
|
||||
}
|
||||
|
||||
envList := make([]string, 0)
|
||||
for k, v := range env {
|
||||
envList = append(envList, fmt.Sprintf("%s=%s", k, v))
|
||||
}
|
||||
executors = append(executors, container.NewDockerRunExecutor(container.NewDockerRunExecutorInput{
|
||||
|
||||
var cmd, entrypoint []string
|
||||
if action.Args != nil {
|
||||
cmd = action.Args.Split()
|
||||
}
|
||||
if action.Runs != nil {
|
||||
entrypoint = action.Runs.Split()
|
||||
}
|
||||
*executors = append(*executors, container.NewDockerRunExecutor(container.NewDockerRunExecutorInput{
|
||||
DockerExecutorInput: in,
|
||||
Cmd: action.Args,
|
||||
Entrypoint: action.Runs,
|
||||
Cmd: cmd,
|
||||
Entrypoint: entrypoint,
|
||||
Image: image,
|
||||
WorkingDir: "/github/workspace",
|
||||
Env: envList,
|
||||
Name: runner.createContainerName(actionName),
|
||||
Name: runner.createContainerName(action.Identifier),
|
||||
Binds: []string{
|
||||
fmt.Sprintf("%s:%s", runner.config.WorkingDir, "/github/workspace"),
|
||||
fmt.Sprintf("%s:%s", runner.tempDir, "/github/home"),
|
||||
|
@ -99,13 +146,17 @@ func (runner *runnerImpl) newActionExecutor(actionName string) common.Executor {
|
|||
ReuseContainers: runner.config.ReuseContainers,
|
||||
}))
|
||||
|
||||
return common.NewPipelineExecutor(executors...)
|
||||
return nil
|
||||
}
|
||||
|
||||
func (runner *runnerImpl) applyEnvironment(env map[string]string) {
|
||||
repoPath := runner.config.WorkingDir
|
||||
|
||||
_, workflowName, _ := runner.workflows.getWorkflow(runner.config.EventName)
|
||||
workflows := runner.workflowConfig.GetWorkflows(runner.config.EventName)
|
||||
if len(workflows) == 0 {
|
||||
return
|
||||
}
|
||||
workflowName := workflows[0].Identifier
|
||||
|
||||
env["HOME"] = "/github/home"
|
||||
env["GITHUB_ACTOR"] = "nektos/act"
|
||||
|
|
|
@ -1,91 +1,47 @@
|
|||
package actions
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"path/filepath"
|
||||
"context"
|
||||
"testing"
|
||||
|
||||
"github.com/nektos/act/common"
|
||||
log "github.com/sirupsen/logrus"
|
||||
"github.com/stretchr/testify/assert"
|
||||
"gotest.tools/assert"
|
||||
)
|
||||
|
||||
func TestParseImageReference(t *testing.T) {
|
||||
log.SetLevel(log.DebugLevel)
|
||||
tables := []struct {
|
||||
refIn string
|
||||
refOut string
|
||||
ok bool
|
||||
}{
|
||||
{"docker://myhost.com/foo/bar", "myhost.com/foo/bar", true},
|
||||
{"docker://ubuntu", "ubuntu", true},
|
||||
{"docker://ubuntu:18.04", "ubuntu:18.04", true},
|
||||
{"docker://cibuilds/hugo:0.53", "cibuilds/hugo:0.53", true},
|
||||
{"http://google.com:8080", "", false},
|
||||
{"./foo", "", false},
|
||||
func TestRunEvent(t *testing.T) {
|
||||
if testing.Short() {
|
||||
t.Skip("skipping integration test")
|
||||
}
|
||||
|
||||
tables := []struct {
|
||||
workflowPath string
|
||||
eventName string
|
||||
errorMessage string
|
||||
}{
|
||||
{"basic.workflow", "push", ""},
|
||||
{"pipe.workflow", "push", ""},
|
||||
{"fail.workflow", "push", "exit with `FAILURE`: 1"},
|
||||
{"regex.workflow", "push", "exit with `NEUTRAL`: 78"},
|
||||
{"gitref.workflow", "push", ""},
|
||||
{"env.workflow", "push", ""},
|
||||
}
|
||||
log.SetLevel(log.DebugLevel)
|
||||
|
||||
for _, table := range tables {
|
||||
refOut, ok := parseImageReference(table.refIn)
|
||||
assert.Equal(t, table.refOut, refOut)
|
||||
assert.Equal(t, table.ok, ok)
|
||||
}
|
||||
runnerConfig := &RunnerConfig{
|
||||
Ctx: context.Background(),
|
||||
WorkflowPath: table.workflowPath,
|
||||
WorkingDir: "testdata",
|
||||
EventName: table.eventName,
|
||||
}
|
||||
runner, err := NewRunner(runnerConfig)
|
||||
assert.NilError(t, err, table.workflowPath)
|
||||
|
||||
}
|
||||
|
||||
func TestParseImageLocal(t *testing.T) {
|
||||
log.SetLevel(log.DebugLevel)
|
||||
tables := []struct {
|
||||
pathIn string
|
||||
contextDir string
|
||||
refTag string
|
||||
ok bool
|
||||
}{
|
||||
{"docker://myhost.com/foo/bar", "", "", false},
|
||||
{"http://google.com:8080", "", "", false},
|
||||
{"example/action1", "", "", false},
|
||||
{"./example/action1", "/example/action1", "action1:", true},
|
||||
}
|
||||
|
||||
revision, _, err := common.FindGitRevision(".")
|
||||
assert.Nil(t, err)
|
||||
basedir, err := filepath.Abs("..")
|
||||
assert.Nil(t, err)
|
||||
for _, table := range tables {
|
||||
contextDir, refTag, ok := parseImageLocal(basedir, table.pathIn)
|
||||
assert.Equal(t, table.ok, ok, "ok match for %s", table.pathIn)
|
||||
if ok {
|
||||
assert.Equal(t, fmt.Sprintf("%s%s", basedir, table.contextDir), contextDir, "context dir doesn't match for %s", table.pathIn)
|
||||
assert.Equal(t, fmt.Sprintf("%s%s", table.refTag, revision), refTag)
|
||||
err = runner.RunEvent()
|
||||
if table.errorMessage == "" {
|
||||
assert.NilError(t, err, table.workflowPath)
|
||||
} else {
|
||||
assert.Error(t, err, table.errorMessage)
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
func TestParseImageGithub(t *testing.T) {
|
||||
log.SetLevel(log.DebugLevel)
|
||||
tables := []struct {
|
||||
image string
|
||||
cloneURL string
|
||||
ref string
|
||||
path string
|
||||
ok bool
|
||||
}{
|
||||
{"nektos/act", "https://github.com/nektos/act", "master", ".", true},
|
||||
{"nektos/act/foo", "https://github.com/nektos/act", "master", "foo", true},
|
||||
{"nektos/act@xxxxx", "https://github.com/nektos/act", "xxxxx", ".", true},
|
||||
{"nektos/act/bar/baz@zzzzz", "https://github.com/nektos/act", "zzzzz", "bar/baz", true},
|
||||
{"assimovt/actions-github-deploy/github-deploy@deployment-status-metadata", "https://github.com/assimovt/actions-github-deploy", "deployment-status-metadata", "github-deploy", true},
|
||||
{"nektos/zzzzundefinedzzzz", "", "", "", false},
|
||||
}
|
||||
|
||||
for _, table := range tables {
|
||||
cloneURL, ref, path, ok := parseImageGithub(table.image)
|
||||
assert.Equal(t, table.ok, ok, "ok match for %s", table.image)
|
||||
if ok {
|
||||
assert.Equal(t, table.cloneURL, cloneURL.String())
|
||||
assert.Equal(t, table.ref, ref)
|
||||
assert.Equal(t, table.path, path)
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -10,7 +10,7 @@ action "build" {
|
|||
|
||||
action "test" {
|
||||
uses = "docker://ubuntu:18.04"
|
||||
args = "echo 'test'"
|
||||
args = "env"
|
||||
needs = ["build"]
|
||||
}
|
||||
|
9
actions/testdata/env.workflow
vendored
Normal file
9
actions/testdata/env.workflow
vendored
Normal file
|
@ -0,0 +1,9 @@
|
|||
workflow "test" {
|
||||
on = "push"
|
||||
resolves = ["test-action"]
|
||||
}
|
||||
|
||||
action "test-action" {
|
||||
uses = "docker://alpine:3.9"
|
||||
runs = ["sh", "-c", "echo $GITHUB_REPOSITORY | grep '^nektos/act$'"]
|
||||
}
|
13
actions/testdata/fail.workflow
vendored
Normal file
13
actions/testdata/fail.workflow
vendored
Normal file
|
@ -0,0 +1,13 @@
|
|||
workflow "test" {
|
||||
on = "push"
|
||||
resolves = ["test-action"]
|
||||
}
|
||||
|
||||
action "test-action" {
|
||||
uses = "docker://alpine:3.9"
|
||||
runs = ["sh", "-c", "echo $IN | grep $OUT"]
|
||||
env = {
|
||||
IN = "foo"
|
||||
OUT = "bar"
|
||||
}
|
||||
}
|
14
actions/testdata/gitref.workflow
vendored
Normal file
14
actions/testdata/gitref.workflow
vendored
Normal file
|
@ -0,0 +1,14 @@
|
|||
workflow "New workflow" {
|
||||
on = "push"
|
||||
resolves = ["branch-ref","commit-ref"]
|
||||
}
|
||||
|
||||
action "branch-ref" {
|
||||
uses = "actions/docker/cli@master"
|
||||
args = "version"
|
||||
}
|
||||
|
||||
action "commit-ref" {
|
||||
uses = "actions/docker/cli@c08a5fc9e0286844156fefff2c141072048141f6"
|
||||
args = "version"
|
||||
}
|
13
actions/testdata/pipe.workflow
vendored
Normal file
13
actions/testdata/pipe.workflow
vendored
Normal file
|
@ -0,0 +1,13 @@
|
|||
workflow "test" {
|
||||
on = "push"
|
||||
resolves = ["test-action"]
|
||||
}
|
||||
|
||||
action "test-action" {
|
||||
uses = "docker://alpine:3.9"
|
||||
runs = ["sh", "-c", "echo $IN | grep $OUT"]
|
||||
env = {
|
||||
IN = "foo"
|
||||
OUT = "foo"
|
||||
}
|
||||
}
|
9
actions/testdata/regex.workflow
vendored
Normal file
9
actions/testdata/regex.workflow
vendored
Normal file
|
@ -0,0 +1,9 @@
|
|||
workflow "New workflow" {
|
||||
on = "push"
|
||||
resolves = ["filter-version-before-deploy"]
|
||||
}
|
||||
|
||||
action "filter-version-before-deploy" {
|
||||
uses = "actions/bin/filter@master"
|
||||
args = "tag z?[0-9]+\\.[0-9]+\\.[0-9]+"
|
||||
}
|
|
@ -6,7 +6,6 @@ import (
|
|||
"errors"
|
||||
"fmt"
|
||||
"io/ioutil"
|
||||
"net/url"
|
||||
"os"
|
||||
"path"
|
||||
"path/filepath"
|
||||
|
@ -185,7 +184,7 @@ func findGitDirectory(fromFile string) (string, error) {
|
|||
|
||||
// NewGitCloneExecutorInput the input for the NewGitCloneExecutor
|
||||
type NewGitCloneExecutorInput struct {
|
||||
URL *url.URL
|
||||
URL string
|
||||
Ref string
|
||||
Dir string
|
||||
Logger *log.Entry
|
||||
|
@ -195,8 +194,8 @@ type NewGitCloneExecutorInput struct {
|
|||
// NewGitCloneExecutor creates an executor to clone git repos
|
||||
func NewGitCloneExecutor(input NewGitCloneExecutorInput) Executor {
|
||||
return func() error {
|
||||
input.Logger.Infof("git clone '%s'", input.URL.String())
|
||||
input.Logger.Debugf(" cloning %s to %s", input.URL.String(), input.Dir)
|
||||
input.Logger.Infof("git clone '%s' # ref=%s", input.URL, input.Ref)
|
||||
input.Logger.Debugf(" cloning %s to %s", input.URL, input.Dir)
|
||||
|
||||
if input.Dryrun {
|
||||
return nil
|
||||
|
@ -210,11 +209,12 @@ func NewGitCloneExecutor(input NewGitCloneExecutorInput) Executor {
|
|||
r, err := git.PlainOpen(input.Dir)
|
||||
if err != nil {
|
||||
r, err = git.PlainClone(input.Dir, false, &git.CloneOptions{
|
||||
URL: input.URL.String(),
|
||||
Progress: input.Logger.WriterLevel(log.DebugLevel),
|
||||
ReferenceName: refName,
|
||||
URL: input.URL,
|
||||
Progress: input.Logger.WriterLevel(log.DebugLevel),
|
||||
//ReferenceName: refName,
|
||||
})
|
||||
if err != nil {
|
||||
input.Logger.Errorf("Unable to clone %v %s: %v", input.URL, refName, err)
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
@ -225,17 +225,24 @@ func NewGitCloneExecutor(input NewGitCloneExecutorInput) Executor {
|
|||
}
|
||||
|
||||
err = w.Pull(&git.PullOptions{
|
||||
ReferenceName: refName,
|
||||
Force: true,
|
||||
//ReferenceName: refName,
|
||||
Force: true,
|
||||
})
|
||||
if err != nil && err.Error() != "already up-to-date" {
|
||||
input.Logger.Errorf("Unable to pull %s: %v", refName, err)
|
||||
}
|
||||
input.Logger.Debugf("Cloned %s to %s", input.URL.String(), input.Dir)
|
||||
input.Logger.Debugf("Cloned %s to %s", input.URL, input.Dir)
|
||||
|
||||
hash, err := r.ResolveRevision(plumbing.Revision(input.Ref))
|
||||
if err != nil {
|
||||
input.Logger.Errorf("Unable to resolve %s: %v", input.Ref, err)
|
||||
return err
|
||||
}
|
||||
|
||||
err = w.Checkout(&git.CheckoutOptions{
|
||||
Branch: refName,
|
||||
Force: true,
|
||||
//Branch: refName,
|
||||
Hash: *hash,
|
||||
Force: true,
|
||||
})
|
||||
if err != nil {
|
||||
input.Logger.Errorf("Unable to checkout %s: %v", refName, err)
|
||||
|
|
|
@ -204,7 +204,7 @@ func waitContainer(input NewDockerRunExecutorInput, cli *client.Client, containe
|
|||
if statusCode == 0 {
|
||||
return nil
|
||||
} else if statusCode == 78 {
|
||||
return fmt.Errorf("exiting with `NEUTRAL`: 78")
|
||||
return fmt.Errorf("exit with `NEUTRAL`: 78")
|
||||
}
|
||||
|
||||
return fmt.Errorf("exit with `FAILURE`: %v", statusCode)
|
||||
|
|
|
@ -1,3 +0,0 @@
|
|||
{
|
||||
"foo": "bar"
|
||||
}
|
29
go.mod
29
go.mod
|
@ -4,40 +4,47 @@ require (
|
|||
github.com/Azure/go-ansiterm v0.0.0-20170929234023-d6e3b3328b78 // indirect
|
||||
github.com/Microsoft/go-winio v0.4.11 // indirect
|
||||
github.com/Nvveen/Gotty v0.0.0-20120604004816-cd527374f1e5 // indirect
|
||||
github.com/actions/workflow-parser v1.0.0
|
||||
github.com/containerd/continuity v0.0.0-20181203112020-004b46473808 // indirect
|
||||
github.com/docker/distribution v2.7.0+incompatible // indirect
|
||||
github.com/docker/distribution v2.7.1+incompatible // indirect
|
||||
github.com/docker/docker v1.13.1
|
||||
github.com/docker/go-connections v0.4.0 // indirect
|
||||
github.com/docker/go-units v0.3.3 // indirect
|
||||
github.com/emirpasic/gods v1.12.0 // indirect
|
||||
github.com/go-ini/ini v1.41.0
|
||||
github.com/gogo/protobuf v1.2.0 // indirect
|
||||
github.com/gopherjs/gopherjs v0.0.0-20181103185306-d547d1d9531e // indirect
|
||||
github.com/gorilla/context v1.1.1 // indirect
|
||||
github.com/gorilla/mux v1.6.2 // indirect
|
||||
github.com/hashicorp/hcl v1.0.0
|
||||
github.com/gorilla/mux v1.7.0 // indirect
|
||||
github.com/hashicorp/hcl v1.0.0 // indirect
|
||||
github.com/howeyc/gopass v0.0.0-20170109162249-bf9dde6d0d2c
|
||||
github.com/inconshreveable/mousetrap v1.0.0 // indirect
|
||||
github.com/jtolds/gls v4.2.1+incompatible // indirect
|
||||
github.com/mitchellh/go-homedir v1.1.0 // indirect
|
||||
github.com/opencontainers/go-digest v1.0.0-rc1 // indirect
|
||||
github.com/opencontainers/image-spec v1.0.1 // indirect
|
||||
github.com/opencontainers/runc v0.1.1 // indirect
|
||||
github.com/pkg/errors v0.8.1 // indirect
|
||||
github.com/sirupsen/logrus v1.3.0
|
||||
github.com/smartystreets/assertions v0.0.0-20180927180507-b2de0cb4f26d // indirect
|
||||
github.com/smartystreets/assertions v0.0.0-20190116191733-b6c0e53d7304 // indirect
|
||||
github.com/smartystreets/goconvey v0.0.0-20181108003508-044398e4856c // indirect
|
||||
github.com/soniakeys/graph v0.0.0 // indirect
|
||||
github.com/spf13/cobra v0.0.3
|
||||
github.com/spf13/pflag v1.0.3 // indirect
|
||||
github.com/stretchr/testify v1.3.0
|
||||
golang.org/x/crypto v0.0.0-20190103213133-ff983b9c42bc
|
||||
golang.org/x/net v0.0.0-20181220203305-927f97764cc3 // indirect
|
||||
golang.org/x/crypto v0.0.0-20190131182504-b8fe1690c613
|
||||
golang.org/x/net v0.0.0-20190125091013-d26f9f9a57f3 // indirect
|
||||
golang.org/x/sync v0.0.0-20181221193216-37e7f081c4d4 // indirect
|
||||
golang.org/x/sys v0.0.0-20190102155601-82a175fd1598 // indirect
|
||||
golang.org/x/sys v0.0.0-20190201152629-afcc84fd7533 // indirect
|
||||
golang.org/x/text v0.3.1-0.20180807135948-17ff2d5776d2 // indirect
|
||||
golang.org/x/time v0.0.0-20181108054448-85acf8d2951c // indirect
|
||||
google.golang.org/grpc v1.17.0 // indirect
|
||||
google.golang.org/genproto v0.0.0-20190128161407-8ac453e89fca // indirect
|
||||
google.golang.org/grpc v1.18.0 // indirect
|
||||
gopkg.in/ini.v1 v1.41.0 // indirect
|
||||
gopkg.in/src-d/go-git.v4 v4.8.1
|
||||
gopkg.in/src-d/go-billy.v4 v4.3.0 // indirect
|
||||
gopkg.in/src-d/go-git-fixtures.v3 v3.3.0 // indirect
|
||||
gopkg.in/src-d/go-git.v4 v4.9.1
|
||||
gopkg.in/yaml.v2 v2.2.2
|
||||
gotest.tools v2.2.0+incompatible // indirect
|
||||
gotest.tools v2.2.0+incompatible
|
||||
)
|
||||
|
||||
replace github.com/docker/docker => github.com/docker/engine v0.0.0-20181106193140-f5749085e9cb
|
||||
|
|
57
go.sum
57
go.sum
|
@ -5,6 +5,8 @@ github.com/Microsoft/go-winio v0.4.11 h1:zoIOcVf0xPN1tnMVbTtEdI+P8OofVk3NObnwOQ6
|
|||
github.com/Microsoft/go-winio v0.4.11/go.mod h1:VhR8bwka0BXejwEJY73c50VrPtXAaKcyvVC4A4RozmA=
|
||||
github.com/Nvveen/Gotty v0.0.0-20120604004816-cd527374f1e5 h1:TngWCqHvy9oXAN6lEVMRuU21PR1EtLVZJmdB18Gu3Rw=
|
||||
github.com/Nvveen/Gotty v0.0.0-20120604004816-cd527374f1e5/go.mod h1:lmUJ/7eu/Q8D7ML55dXQrVaamCz2vxCfdQBasLZfHKk=
|
||||
github.com/actions/workflow-parser v1.0.0 h1:Zz2Ke31f3OMYCSzU2pqZSsk/Oz+lWXfEiXMisjxgGcc=
|
||||
github.com/actions/workflow-parser v1.0.0/go.mod h1:jz9ZVl8zUIcjMfDQearQjvUHIBhx9l1ys4keDd6be34=
|
||||
github.com/alcortesm/tgz v0.0.0-20161220082320-9c5fe88206d7 h1:uSoVVbwJiQipAclBbw+8quDsfcvFjOpI5iCf4p/cqCs=
|
||||
github.com/alcortesm/tgz v0.0.0-20161220082320-9c5fe88206d7/go.mod h1:6zEj6s6u/ghQa61ZWa/C2Aw3RkjiTBOix7dkqa1VLIs=
|
||||
github.com/anmitsu/go-shlex v0.0.0-20161002113705-648efa622239 h1:kFOfPq6dUM1hTo4JG6LR5AXSUEsOjtdm0kw0FtQtMJA=
|
||||
|
@ -15,8 +17,8 @@ github.com/containerd/continuity v0.0.0-20181203112020-004b46473808/go.mod h1:GL
|
|||
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
||||
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
|
||||
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
||||
github.com/docker/distribution v2.7.0+incompatible h1:neUDAlf3wX6Ml4HdqTrbcOHXtfRN0TFIwt6YFL7N9RU=
|
||||
github.com/docker/distribution v2.7.0+incompatible/go.mod h1:J2gT2udsDAN96Uj4KfcMRqY0/ypR+oyYUYmja8H+y+w=
|
||||
github.com/docker/distribution v2.7.1+incompatible h1:a5mlkVzth6W5A4fOsS3D2EO5BUmsJpcB+cRlLU7cSug=
|
||||
github.com/docker/distribution v2.7.1+incompatible/go.mod h1:J2gT2udsDAN96Uj4KfcMRqY0/ypR+oyYUYmja8H+y+w=
|
||||
github.com/docker/docker v1.13.1 h1:IkZjBSIc8hBjLpqeAbeE5mca5mNgeatLHBy3GO78BWo=
|
||||
github.com/docker/docker v1.13.1/go.mod h1:eEKB0N0r5NX/I1kEveEz05bcu8tLC/8azJZsviup8Sk=
|
||||
github.com/docker/engine v0.0.0-20181106193140-f5749085e9cb h1:PyjxRdW1mqCmSoxy/6uP01P7CGbsD+woX+oOWbaUPwQ=
|
||||
|
@ -27,6 +29,8 @@ github.com/docker/go-units v0.3.3 h1:Xk8S3Xj5sLGlG5g67hJmYMmUgXv5N4PhkjJHHqrwnTk
|
|||
github.com/docker/go-units v0.3.3/go.mod h1:fgPhTUdO+D/Jk86RDLlptpiXQzgHJF7gydDDbaIK4Dk=
|
||||
github.com/emirpasic/gods v1.9.0 h1:rUF4PuzEjMChMiNsVjdI+SyLu7rEqpQ5reNFnhC7oFo=
|
||||
github.com/emirpasic/gods v1.9.0/go.mod h1:YfzfFFoVP/catgzJb4IKIqXjX78Ha8FMSDh3ymbK86o=
|
||||
github.com/emirpasic/gods v1.12.0 h1:QAUIPSaCu4G+POclxeqb3F+WPpdKqFGlw36+yOzGlrg=
|
||||
github.com/emirpasic/gods v1.12.0/go.mod h1:YfzfFFoVP/catgzJb4IKIqXjX78Ha8FMSDh3ymbK86o=
|
||||
github.com/flynn/go-shlex v0.0.0-20150515145356-3f9db97f8568 h1:BHsljHzVlRcyQhjrss6TZTdY2VfCqZPbv5k3iBFa2ZQ=
|
||||
github.com/flynn/go-shlex v0.0.0-20150515145356-3f9db97f8568/go.mod h1:xEzjJPgXI435gkrCt3MPfRiAkVrwSbHsst4LCFVfpJc=
|
||||
github.com/gliderlabs/ssh v0.1.1 h1:j3L6gSLQalDETeEg/Jg0mGY0/y/N6zI2xX1978P0Uqw=
|
||||
|
@ -37,6 +41,7 @@ github.com/gogo/protobuf v1.2.0 h1:xU6/SpYbvkNYiptHJYEDRseDLvYE7wSqhYYNy0QSUzI=
|
|||
github.com/gogo/protobuf v1.2.0/go.mod h1:r8qH/GZQm5c6nD/R0oafs1akxWv10x8SbQlK7atdtwQ=
|
||||
github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b h1:VKtxabqXZkF25pY9ekfRL6a582T4P37/31XEstQ5p58=
|
||||
github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q=
|
||||
github.com/golang/lint v0.0.0-20180702182130-06c8688daad7/go.mod h1:tluoj9z5200jBnyusfRPU2LqT6J+DAorxEvtC7LHB+E=
|
||||
github.com/golang/mock v1.1.1/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A=
|
||||
github.com/golang/protobuf v1.2.0 h1:P3YflyNX/ehuJFLhxviNdFxQPkGK5cDcApsge1SqnvM=
|
||||
github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
|
||||
|
@ -44,10 +49,8 @@ github.com/google/go-cmp v0.2.0 h1:+dTQ8DZQJz0Mb/HjFlkptS1FeQ4cWSnN941F8aEG4SQ=
|
|||
github.com/google/go-cmp v0.2.0/go.mod h1:oXzfMopK8JAjlY9xF4vHSVASa0yLyX7SntLO5aqRK0M=
|
||||
github.com/gopherjs/gopherjs v0.0.0-20181103185306-d547d1d9531e h1:JKmoR8x90Iww1ks85zJ1lfDGgIiMDuIptTOhJq+zKyg=
|
||||
github.com/gopherjs/gopherjs v0.0.0-20181103185306-d547d1d9531e/go.mod h1:wJfORRmW1u3UXTncJ5qlYoELFm8eSnnEO6hX4iZ3EWY=
|
||||
github.com/gorilla/context v1.1.1 h1:AWwleXJkX/nhcU9bZSnZoi3h/qGYqQAGhq6zZe/aQW8=
|
||||
github.com/gorilla/context v1.1.1/go.mod h1:kBGZzfjB9CEq2AlWe17Uuf7NDRt0dE0s8S51q0aT7Yg=
|
||||
github.com/gorilla/mux v1.6.2 h1:Pgr17XVTNXAk3q/r4CpKzC5xBM/qW1uVLV+IhRZpIIk=
|
||||
github.com/gorilla/mux v1.6.2/go.mod h1:1lud6UwP+6orDFRuTfBEV8e9/aOM/c4fVVCaMa2zaAs=
|
||||
github.com/gorilla/mux v1.7.0 h1:tOSd0UKHQd6urX6ApfOn4XdBMY6Sh1MfxV3kmaazO+U=
|
||||
github.com/gorilla/mux v1.7.0/go.mod h1:1lud6UwP+6orDFRuTfBEV8e9/aOM/c4fVVCaMa2zaAs=
|
||||
github.com/hashicorp/hcl v1.0.0 h1:0Anlzjpi4vEasTeNFn2mLJgTSwt0+6sfsiTG8qcWGx4=
|
||||
github.com/hashicorp/hcl v1.0.0/go.mod h1:E5yfLk+7swimpb2L/Alb/PJmXilQ/rhwaUYs4T20WEQ=
|
||||
github.com/howeyc/gopass v0.0.0-20170109162249-bf9dde6d0d2c h1:kQWxfPIHVLbgLzphqk3QUflDy9QdksZR4ygR807bpy0=
|
||||
|
@ -72,6 +75,8 @@ github.com/kr/text v0.1.0 h1:45sCR5RtlFHMR4UwH9sdQ5TC8v0qDQCHnXt+kaKSTVE=
|
|||
github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI=
|
||||
github.com/mitchellh/go-homedir v1.0.0 h1:vKb8ShqSby24Yrqr/yDYkuFz8d0WUjys40rvnGC8aR0=
|
||||
github.com/mitchellh/go-homedir v1.0.0/go.mod h1:SfyaCUpYCn1Vlf4IUYiD9fPX4A5wJrkLzIz1N1q0pr0=
|
||||
github.com/mitchellh/go-homedir v1.1.0 h1:lukF9ziXFxDFPkA1vsr5zpc1XuPDn/wFntq5mG+4E0Y=
|
||||
github.com/mitchellh/go-homedir v1.1.0/go.mod h1:SfyaCUpYCn1Vlf4IUYiD9fPX4A5wJrkLzIz1N1q0pr0=
|
||||
github.com/opencontainers/go-digest v1.0.0-rc1 h1:WzifXhOVOEOuFYOJAW6aQqW0TooG2iki3E3Ii+WN7gQ=
|
||||
github.com/opencontainers/go-digest v1.0.0-rc1/go.mod h1:cMLVZDEM3+U2I4VmLI6N8jQYUd2OVphdqWwCJHrFt2s=
|
||||
github.com/opencontainers/image-spec v1.0.1 h1:JMemWkRwHx4Zj+fVxWoMCFm/8sYGGrUVojFA6h/TRcI=
|
||||
|
@ -90,10 +95,14 @@ github.com/sergi/go-diff v1.0.0 h1:Kpca3qRNrduNnOQeazBd0ysaKrUJiIuISHxogkT9RPQ=
|
|||
github.com/sergi/go-diff v1.0.0/go.mod h1:0CfEIISq7TuYL3j771MWULgwwjU+GofnZX9QAmXWZgo=
|
||||
github.com/sirupsen/logrus v1.3.0 h1:hI/7Q+DtNZ2kINb6qt/lS+IyXnHQe9e90POfeewL/ME=
|
||||
github.com/sirupsen/logrus v1.3.0/go.mod h1:LxeOpSwHxABJmUn/MG1IvRgCAasNZTLOkJPxbbu5VWo=
|
||||
github.com/smartystreets/assertions v0.0.0-20180927180507-b2de0cb4f26d h1:zE9ykElWQ6/NYmHa3jpm/yHnI4xSofP+UP6SpjHcSeM=
|
||||
github.com/smartystreets/assertions v0.0.0-20180927180507-b2de0cb4f26d/go.mod h1:OnSkiWE9lh6wB0YB77sQom3nweQdgAjqCqsofrRNTgc=
|
||||
github.com/smartystreets/assertions v0.0.0-20190116191733-b6c0e53d7304 h1:Jpy1PXuP99tXNrhbq2BaPz9B+jNAvH1JPQQpG/9GCXY=
|
||||
github.com/smartystreets/assertions v0.0.0-20190116191733-b6c0e53d7304/go.mod h1:OnSkiWE9lh6wB0YB77sQom3nweQdgAjqCqsofrRNTgc=
|
||||
github.com/smartystreets/goconvey v0.0.0-20181108003508-044398e4856c h1:Ho+uVpkel/udgjbwB5Lktg9BtvJSh2DT0Hi6LPSyI2w=
|
||||
github.com/smartystreets/goconvey v0.0.0-20181108003508-044398e4856c/go.mod h1:XDJAKZRPZ1CvBcN2aX5YOUTYGHki24fSF0Iv48Ibg0s=
|
||||
github.com/soniakeys/bits v1.0.0 h1:Rune9VFefdJvLE0Q5iRCVGiKdSu2iDihs2I6SCm7evw=
|
||||
github.com/soniakeys/bits v1.0.0/go.mod h1:7yJHB//UizrUr64VFneewK6SX5oeCf0SMbDYe2ey1JA=
|
||||
github.com/soniakeys/graph v0.0.0 h1:C/Rr8rv9wbhZIsYHcWJFoI84pkipJocMYdRteE+/PQA=
|
||||
github.com/soniakeys/graph v0.0.0/go.mod h1:lxpIbor/bIzWUAqvt1Dx92Hr63uWeyuEAbPnsjYbVwM=
|
||||
github.com/spf13/cobra v0.0.3 h1:ZlrZ4XsMRm04Fr5pSFxBgfND2EBVa1nLpiy1stUsX/8=
|
||||
github.com/spf13/cobra v0.0.3/go.mod h1:1l0Ry5zgKvJasoi3XT1TypsSe7PqH0Sj9dhYf7v3XqQ=
|
||||
github.com/spf13/pflag v1.0.3 h1:zPAT6CGy6wXeQ7NtTnaTerfKOsV6V6F8agHXFiazDkg=
|
||||
|
@ -111,13 +120,16 @@ github.com/xanzy/ssh-agent v0.2.0 h1:Adglfbi5p9Z0BmK2oKU9nTG+zKfniSfnaMYB+ULd+Ro
|
|||
github.com/xanzy/ssh-agent v0.2.0/go.mod h1:0NyE30eGUDliuLEHJgYte/zncp2zdTStcOnWhgSqHD8=
|
||||
golang.org/x/crypto v0.0.0-20180904163835-0709b304e793 h1:u+LnwYTOOW7Ukr/fppxEb1Nwz0AtPflrblfvUudpo+I=
|
||||
golang.org/x/crypto v0.0.0-20180904163835-0709b304e793/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4=
|
||||
golang.org/x/crypto v0.0.0-20190103213133-ff983b9c42bc h1:F5tKCVGp+MUAHhKp5MZtGqAlGX3+oCsiL1Q629FL90M=
|
||||
golang.org/x/crypto v0.0.0-20190103213133-ff983b9c42bc/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4=
|
||||
golang.org/x/crypto v0.0.0-20190131182504-b8fe1690c613 h1:MQ/ZZiDsUapFFiMS+vzwXkCTeEKaum+Do5rINYJDmxc=
|
||||
golang.org/x/crypto v0.0.0-20190131182504-b8fe1690c613/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4=
|
||||
golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
|
||||
golang.org/x/lint v0.0.0-20180702182130-06c8688daad7/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE=
|
||||
golang.org/x/lint v0.0.0-20181026193005-c67002cb31c3/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE=
|
||||
golang.org/x/net v0.0.0-20180826012351-8a410e7b638d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
|
||||
golang.org/x/net v0.0.0-20180906233101-161cd47e91fd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
|
||||
golang.org/x/net v0.0.0-20181220203305-927f97764cc3 h1:eH6Eip3UpmR+yM/qI9Ijluzb1bNv/cAU/n+6l8tRSis=
|
||||
golang.org/x/net v0.0.0-20181220203305-927f97764cc3/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
|
||||
golang.org/x/net v0.0.0-20181106065722-10aee1819953/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
|
||||
golang.org/x/net v0.0.0-20190125091013-d26f9f9a57f3 h1:ulvT7fqt0yHWzpJwI57MezWnYDVpCAYBVuYst/L+fAY=
|
||||
golang.org/x/net v0.0.0-20190125091013-d26f9f9a57f3/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
|
||||
golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U=
|
||||
golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f h1:wMNYb4v58l5UBM7MYRLPG6ZhfOqbKu7X5eyFl8ZhKvA=
|
||||
golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
|
@ -127,10 +139,12 @@ golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5h
|
|||
golang.org/x/sys v0.0.0-20180903190138-2b024373dcd9/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||
golang.org/x/sys v0.0.0-20180905080454-ebe1bf3edb33 h1:I6FyU15t786LL7oL/hn43zqTuEGr4PN7F4XJ1p4E3Y8=
|
||||
golang.org/x/sys v0.0.0-20180905080454-ebe1bf3edb33/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||
golang.org/x/sys v0.0.0-20190102155601-82a175fd1598 h1:S8GOgffXV1X3fpVG442QRfWOt0iFl79eHJ7OPt725bo=
|
||||
golang.org/x/sys v0.0.0-20190102155601-82a175fd1598/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||
golang.org/x/sys v0.0.0-20190201152629-afcc84fd7533 h1:bLfqnzrpeG4usq5OvMCrwTdmMJ6aTmlCuo1eKl0mhkI=
|
||||
golang.org/x/sys v0.0.0-20190201152629-afcc84fd7533/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||
golang.org/x/text v0.3.0 h1:g61tztE5qeGQ89tm6NTjjM9VPIm088od1l6aSorWRWg=
|
||||
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
|
||||
golang.org/x/text v0.3.1-0.20180807135948-17ff2d5776d2 h1:z99zHgr7hKfrUcX/KsoJk5FJfjTceCKIp96+biqP4To=
|
||||
golang.org/x/text v0.3.1-0.20180807135948-17ff2d5776d2/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
|
||||
golang.org/x/time v0.0.0-20181108054448-85acf8d2951c h1:fqgJT0MGcGpPgpWU7VRdRjuArfcOvC4AoJmILihzhDg=
|
||||
golang.org/x/time v0.0.0-20181108054448-85acf8d2951c/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
|
||||
golang.org/x/tools v0.0.0-20180828015842-6cd1fcedba52 h1:JG/0uqcGdTNgq7FdU+61l5Pdmb8putNZlXb65bJBROs=
|
||||
|
@ -138,8 +152,11 @@ golang.org/x/tools v0.0.0-20180828015842-6cd1fcedba52/go.mod h1:n7NCudcB/nEzxVGm
|
|||
google.golang.org/appengine v1.1.0/go.mod h1:EbEs0AVv82hx2wNQdGPgUI5lhzA/G0D9YwlJXL52JkM=
|
||||
google.golang.org/genproto v0.0.0-20180817151627-c66870c02cf8 h1:Nw54tB0rB7hY/N0NQvRW8DG4Yk3Q6T9cu9RcFQDu1tc=
|
||||
google.golang.org/genproto v0.0.0-20180817151627-c66870c02cf8/go.mod h1:JiN7NxoALGmiZfu7CAH4rXhgtRTLTxftemlI0sWmxmc=
|
||||
google.golang.org/grpc v1.17.0 h1:TRJYBgMclJvGYn2rIMjj+h9KtMt5r1Ij7ODVRIZkwhk=
|
||||
google.golang.org/grpc v1.17.0/go.mod h1:6QZJwpn2B+Zp71q/5VxRsJ6NXXVCE5NRUHRo+f3cWCs=
|
||||
google.golang.org/genproto v0.0.0-20190128161407-8ac453e89fca h1:L1odPN6KVjhk0Lbg41BKcjGjP7ELTvh/qDcyh6hEfv0=
|
||||
google.golang.org/genproto v0.0.0-20190128161407-8ac453e89fca/go.mod h1:L3J43x8/uS+qIUoksaLKe6OS3nUKxOKuIFz1sl2/jx4=
|
||||
google.golang.org/grpc v1.16.0/go.mod h1:0JHn/cJsOMiMfNA9+DeHDlAU7KAAB5GDlYFpa9MZMio=
|
||||
google.golang.org/grpc v1.18.0 h1:IZl7mfBGfbhYx2p2rKRtYgDFw6SBz+kclmxYrCksPPA=
|
||||
google.golang.org/grpc v1.18.0/go.mod h1:6QZJwpn2B+Zp71q/5VxRsJ6NXXVCE5NRUHRo+f3cWCs=
|
||||
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM=
|
||||
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
||||
gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127 h1:qIbj1fsPNlZgppZ+VLlY7N33q108Sa+fhmuc+sWQYwY=
|
||||
|
@ -148,10 +165,14 @@ gopkg.in/ini.v1 v1.41.0 h1:Ka3ViY6gNYSKiVy71zXBEqKplnV35ImDLVG+8uoIklE=
|
|||
gopkg.in/ini.v1 v1.41.0/go.mod h1:pNLf8WUiyNEtQjuu5G5vTm06TEv9tsIgeAvK8hOrP4k=
|
||||
gopkg.in/src-d/go-billy.v4 v4.2.1 h1:omN5CrMrMcQ+4I8bJ0wEhOBPanIRWzFC953IiXKdYzo=
|
||||
gopkg.in/src-d/go-billy.v4 v4.2.1/go.mod h1:tm33zBoOwxjYHZIE+OV8bxTWFMJLrconzFMd38aARFk=
|
||||
gopkg.in/src-d/go-billy.v4 v4.3.0 h1:KtlZ4c1OWbIs4jCv5ZXrTqG8EQocr0g/d4DjNg70aek=
|
||||
gopkg.in/src-d/go-billy.v4 v4.3.0/go.mod h1:tm33zBoOwxjYHZIE+OV8bxTWFMJLrconzFMd38aARFk=
|
||||
gopkg.in/src-d/go-git-fixtures.v3 v3.1.1 h1:XWW/s5W18RaJpmo1l0IYGqXKuJITWRFuA45iOf1dKJs=
|
||||
gopkg.in/src-d/go-git-fixtures.v3 v3.1.1/go.mod h1:dLBcvytrw/TYZsNTWCnkNF2DSIlzWYqTe3rJR56Ac7g=
|
||||
gopkg.in/src-d/go-git.v4 v4.8.1 h1:aAyBmkdE1QUUEHcP4YFCGKmsMQRAuRmUcPEQR7lOAa0=
|
||||
gopkg.in/src-d/go-git.v4 v4.8.1/go.mod h1:Vtut8izDyrM8BUVQnzJ+YvmNcem2J89EmfZYCkLokZk=
|
||||
gopkg.in/src-d/go-git-fixtures.v3 v3.3.0 h1:AxUOwLW3at53ysFqs0Lg+H+8KSQXl7AEHBvWj8wEsT8=
|
||||
gopkg.in/src-d/go-git-fixtures.v3 v3.3.0/go.mod h1:dLBcvytrw/TYZsNTWCnkNF2DSIlzWYqTe3rJR56Ac7g=
|
||||
gopkg.in/src-d/go-git.v4 v4.9.1 h1:0oKHJZY8tM7B71378cfTg2c5jmWyNlXvestTT6WfY+4=
|
||||
gopkg.in/src-d/go-git.v4 v4.9.1/go.mod h1:Vtut8izDyrM8BUVQnzJ+YvmNcem2J89EmfZYCkLokZk=
|
||||
gopkg.in/warnings.v0 v0.1.2 h1:wFXVbFY8DY5/xOe1ECiWdKCzZlxgshcYVNkBHstARME=
|
||||
gopkg.in/warnings.v0 v0.1.2/go.mod h1:jksf8JmL6Qr/oQM2OXTHunEvvTAsrWBLb6OOjuVWRNI=
|
||||
gopkg.in/yaml.v2 v2.2.2 h1:ZCJp+EgiOT7lHqUV2J862kp8Qj64Jo6az82+3Td9dZw=
|
||||
|
|
21
vendor/github.com/actions/workflow-parser/LICENSE
generated
vendored
Normal file
21
vendor/github.com/actions/workflow-parser/LICENSE
generated
vendored
Normal file
|
@ -0,0 +1,21 @@
|
|||
MIT License
|
||||
|
||||
Copyright (c) 2019 GitHub
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
in the Software without restriction, including without limitation the rights
|
||||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
copies of the Software, and to permit persons to whom the Software is
|
||||
furnished to do so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in all
|
||||
copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
SOFTWARE.
|
38
vendor/github.com/actions/workflow-parser/model/command.go
generated
vendored
Normal file
38
vendor/github.com/actions/workflow-parser/model/command.go
generated
vendored
Normal file
|
@ -0,0 +1,38 @@
|
|||
package model
|
||||
|
||||
import (
|
||||
"strings"
|
||||
)
|
||||
|
||||
// Command represents the optional "runs" and "args" attributes.
|
||||
// Each one takes one of two forms:
|
||||
// - runs="entrypoint arg1 arg2 ..."
|
||||
// - runs=[ "entrypoint", "arg1", "arg2", ... ]
|
||||
type Command interface {
|
||||
isCommand()
|
||||
Split() []string
|
||||
}
|
||||
|
||||
// StringCommand represents the string based form of the "runs" or "args"
|
||||
// attribute.
|
||||
// - runs="entrypoint arg1 arg2 ..."
|
||||
type StringCommand struct {
|
||||
Value string
|
||||
}
|
||||
|
||||
// ListCommand represents the list based form of the "runs" or "args" attribute.
|
||||
// - runs=[ "entrypoint", "arg1", "arg2", ... ]
|
||||
type ListCommand struct {
|
||||
Values []string
|
||||
}
|
||||
|
||||
func (s *StringCommand) isCommand() {}
|
||||
func (l *ListCommand) isCommand() {}
|
||||
|
||||
func (s *StringCommand) Split() []string {
|
||||
return strings.Fields(s.Value)
|
||||
}
|
||||
|
||||
func (l *ListCommand) Split() []string {
|
||||
return l.Values
|
||||
}
|
64
vendor/github.com/actions/workflow-parser/model/configuration.go
generated
vendored
Normal file
64
vendor/github.com/actions/workflow-parser/model/configuration.go
generated
vendored
Normal file
|
@ -0,0 +1,64 @@
|
|||
package model
|
||||
|
||||
import (
|
||||
"strings"
|
||||
)
|
||||
|
||||
// Configuration is a parsed main.workflow file
|
||||
type Configuration struct {
|
||||
Actions []*Action
|
||||
Workflows []*Workflow
|
||||
}
|
||||
|
||||
// Action represents a single "action" stanza in a .workflow file.
|
||||
type Action struct {
|
||||
Identifier string
|
||||
Uses Uses
|
||||
Runs, Args Command
|
||||
Needs []string
|
||||
Env map[string]string
|
||||
Secrets []string
|
||||
}
|
||||
|
||||
// Workflow represents a single "workflow" stanza in a .workflow file.
|
||||
type Workflow struct {
|
||||
Identifier string
|
||||
On string
|
||||
Resolves []string
|
||||
}
|
||||
|
||||
// GetAction looks up action by identifier.
|
||||
//
|
||||
// If the action is not found, nil is returned.
|
||||
func (c *Configuration) GetAction(id string) *Action {
|
||||
for _, action := range c.Actions {
|
||||
if action.Identifier == id {
|
||||
return action
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// GetWorkflow looks up a workflow by identifier.
|
||||
//
|
||||
// If the workflow is not found, nil is returned.
|
||||
func (c *Configuration) GetWorkflow(id string) *Workflow {
|
||||
for _, workflow := range c.Workflows {
|
||||
if workflow.Identifier == id {
|
||||
return workflow
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// GetWorkflows gets all Workflow structures that match a given type of event.
|
||||
// e.g., GetWorkflows("push")
|
||||
func (c *Configuration) GetWorkflows(eventType string) []*Workflow {
|
||||
var ret []*Workflow
|
||||
for _, workflow := range c.Workflows {
|
||||
if strings.EqualFold(workflow.On, eventType) {
|
||||
ret = append(ret, workflow)
|
||||
}
|
||||
}
|
||||
return ret
|
||||
}
|
57
vendor/github.com/actions/workflow-parser/model/uses.go
generated
vendored
Normal file
57
vendor/github.com/actions/workflow-parser/model/uses.go
generated
vendored
Normal file
|
@ -0,0 +1,57 @@
|
|||
package model
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
)
|
||||
|
||||
type Uses interface {
|
||||
fmt.Stringer
|
||||
isUses()
|
||||
}
|
||||
|
||||
// UsesDockerImage represents `uses = "docker://<image>"`
|
||||
type UsesDockerImage struct {
|
||||
Image string
|
||||
}
|
||||
|
||||
// UsesRepository represents `uses = "<owner>/<repo>[/<path>]@<ref>"`
|
||||
type UsesRepository struct {
|
||||
Repository string
|
||||
Path string
|
||||
Ref string
|
||||
}
|
||||
|
||||
// UsesPath represents `uses = "./<path>"`
|
||||
type UsesPath struct {
|
||||
Path string
|
||||
}
|
||||
|
||||
// UsesInvalid represents any invalid `uses = "<raw>"` value
|
||||
type UsesInvalid struct {
|
||||
Raw string
|
||||
}
|
||||
|
||||
func (u *UsesDockerImage) isUses() {}
|
||||
func (u *UsesRepository) isUses() {}
|
||||
func (u *UsesPath) isUses() {}
|
||||
func (u *UsesInvalid) isUses() {}
|
||||
|
||||
func (u *UsesDockerImage) String() string {
|
||||
return fmt.Sprintf("docker://%s", u.Image)
|
||||
}
|
||||
|
||||
func (u *UsesRepository) String() string {
|
||||
if u.Path == "" {
|
||||
return fmt.Sprintf("%s@%s", u.Repository, u.Ref)
|
||||
}
|
||||
|
||||
return fmt.Sprintf("%s/%s@%s", u.Repository, u.Path, u.Ref)
|
||||
}
|
||||
|
||||
func (u *UsesPath) String() string {
|
||||
return fmt.Sprintf("./%s", u.Path)
|
||||
}
|
||||
|
||||
func (u *UsesInvalid) String() string {
|
||||
return u.Raw
|
||||
}
|
136
vendor/github.com/actions/workflow-parser/parser/errors.go
generated
vendored
Normal file
136
vendor/github.com/actions/workflow-parser/parser/errors.go
generated
vendored
Normal file
|
@ -0,0 +1,136 @@
|
|||
package parser
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"fmt"
|
||||
"sort"
|
||||
"strconv"
|
||||
"strings"
|
||||
|
||||
"github.com/actions/workflow-parser/model"
|
||||
)
|
||||
|
||||
type Error struct {
|
||||
message string
|
||||
Errors []*ParseError
|
||||
Actions []*model.Action
|
||||
Workflows []*model.Workflow
|
||||
}
|
||||
|
||||
func (e *Error) Error() string {
|
||||
buffer := bytes.NewBuffer(nil)
|
||||
buffer.WriteString(e.message)
|
||||
for _, pe := range e.Errors {
|
||||
buffer.WriteString("\n ")
|
||||
buffer.WriteString(pe.Error())
|
||||
}
|
||||
return buffer.String()
|
||||
}
|
||||
|
||||
// FirstError searches a Configuration for the first error at or above a
|
||||
// given severity level. Checking the return value against nil is a good
|
||||
// way to see if the file has any errors at or above the given severity.
|
||||
// A caller intending to execute the file might check for
|
||||
// `errors.FirstError(parser.WARNING)`, while a caller intending to
|
||||
// display the file might check for `errors.FirstError(parser.FATAL)`.
|
||||
func (e *Error) FirstError(severity Severity) error {
|
||||
for _, pe := range e.Errors {
|
||||
if pe.Severity >= severity {
|
||||
return pe
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// ParseError represents an error identified by the parser, either syntactic
|
||||
// (HCL) or semantic (.workflow) in nature. There are fields for location
|
||||
// (File, Line, Column), severity, and base error string. The `Error()`
|
||||
// function on this type concatenates whatever bits of the location are
|
||||
// available with the message. The severity is only used for filtering.
|
||||
type ParseError struct {
|
||||
message string
|
||||
Pos ErrorPos
|
||||
Severity Severity
|
||||
}
|
||||
|
||||
// ErrorPos represents the location of an error in a user's workflow
|
||||
// file(s).
|
||||
type ErrorPos struct {
|
||||
File string
|
||||
Line int
|
||||
Column int
|
||||
}
|
||||
|
||||
// newFatal creates a new error at the FATAL level, indicating that the
|
||||
// file is so broken it should not be displayed.
|
||||
func newFatal(pos ErrorPos, format string, a ...interface{}) *ParseError {
|
||||
return &ParseError{
|
||||
message: fmt.Sprintf(format, a...),
|
||||
Pos: pos,
|
||||
Severity: FATAL,
|
||||
}
|
||||
}
|
||||
|
||||
// newError creates a new error at the ERROR level, indicating that the
|
||||
// file can be displayed but cannot be run.
|
||||
func newError(pos ErrorPos, format string, a ...interface{}) *ParseError {
|
||||
return &ParseError{
|
||||
message: fmt.Sprintf(format, a...),
|
||||
Pos: pos,
|
||||
Severity: ERROR,
|
||||
}
|
||||
}
|
||||
|
||||
// newWarning creates a new error at the WARNING level, indicating that
|
||||
// the file might be runnable but might not execute as intended.
|
||||
func newWarning(pos ErrorPos, format string, a ...interface{}) *ParseError {
|
||||
return &ParseError{
|
||||
message: fmt.Sprintf(format, a...),
|
||||
Pos: pos,
|
||||
Severity: WARNING,
|
||||
}
|
||||
}
|
||||
|
||||
func (e *ParseError) Error() string {
|
||||
var sb strings.Builder
|
||||
if e.Pos.Line != 0 {
|
||||
sb.WriteString("Line ") // nolint: errcheck
|
||||
sb.WriteString(strconv.Itoa(e.Pos.Line)) // nolint: errcheck
|
||||
sb.WriteString(": ") // nolint: errcheck
|
||||
}
|
||||
if sb.Len() > 0 {
|
||||
sb.WriteString(e.message) // nolint: errcheck
|
||||
return sb.String()
|
||||
}
|
||||
return e.message
|
||||
}
|
||||
|
||||
const (
|
||||
_ = iota
|
||||
|
||||
// WARNING indicates a mistake that might affect correctness
|
||||
WARNING
|
||||
|
||||
// ERROR indicates a mistake that prevents execution of any workflows in the file
|
||||
ERROR
|
||||
|
||||
// FATAL indicates a mistake that prevents even drawing the file
|
||||
FATAL
|
||||
)
|
||||
|
||||
// Severity represents the level of an error encountered while parsing a
|
||||
// workflow file. See the comments for WARNING, ERROR, and FATAL, above.
|
||||
type Severity int
|
||||
|
||||
type errorList []*ParseError
|
||||
|
||||
func (a errorList) Len() int { return len(a) }
|
||||
func (a errorList) Swap(i, j int) { a[i], a[j] = a[j], a[i] }
|
||||
func (a errorList) Less(i, j int) bool { return a[i].Pos.Line < a[j].Pos.Line }
|
||||
|
||||
// sortErrors sorts the errors reported by the parser. Do this after
|
||||
// parsing is complete. The sort is stable, so order is preserved within
|
||||
// a single line: left to right, syntax errors before validation errors.
|
||||
func (errors errorList) sort() {
|
||||
sort.Stable(errors)
|
||||
}
|
42
vendor/github.com/actions/workflow-parser/parser/events.go
generated
vendored
Normal file
42
vendor/github.com/actions/workflow-parser/parser/events.go
generated
vendored
Normal file
|
@ -0,0 +1,42 @@
|
|||
package parser
|
||||
|
||||
import (
|
||||
"strings"
|
||||
)
|
||||
|
||||
// isAllowedEventType returns true if the event type is supported.
|
||||
func isAllowedEventType(eventType string) bool {
|
||||
_, ok := eventTypeWhitelist[strings.ToLower(eventType)]
|
||||
return ok
|
||||
}
|
||||
|
||||
// https://developer.github.com/actions/creating-workflows/workflow-configuration-options/#events-supported-in-workflow-files
|
||||
var eventTypeWhitelist = map[string]struct{}{
|
||||
"check_run": {},
|
||||
"check_suite": {},
|
||||
"commit_comment": {},
|
||||
"create": {},
|
||||
"delete": {},
|
||||
"deployment": {},
|
||||
"deployment_status": {},
|
||||
"fork": {},
|
||||
"gollum": {},
|
||||
"issue_comment": {},
|
||||
"issues": {},
|
||||
"label": {},
|
||||
"member": {},
|
||||
"milestone": {},
|
||||
"page_build": {},
|
||||
"project_card": {},
|
||||
"project_column": {},
|
||||
"project": {},
|
||||
"public": {},
|
||||
"pull_request_review_comment": {},
|
||||
"pull_request_review": {},
|
||||
"pull_request": {},
|
||||
"push": {},
|
||||
"release": {},
|
||||
"repository_dispatch": {},
|
||||
"status": {},
|
||||
"watch": {},
|
||||
}
|
15
vendor/github.com/actions/workflow-parser/parser/opts.go
generated
vendored
Normal file
15
vendor/github.com/actions/workflow-parser/parser/opts.go
generated
vendored
Normal file
|
@ -0,0 +1,15 @@
|
|||
package parser
|
||||
|
||||
type OptionFunc func(*Parser)
|
||||
|
||||
func WithSuppressWarnings() OptionFunc {
|
||||
return func(ps *Parser) {
|
||||
ps.suppressSeverity = WARNING
|
||||
}
|
||||
}
|
||||
|
||||
func WithSuppressErrors() OptionFunc {
|
||||
return func(ps *Parser) {
|
||||
ps.suppressSeverity = ERROR
|
||||
}
|
||||
}
|
807
vendor/github.com/actions/workflow-parser/parser/parser.go
generated
vendored
Normal file
807
vendor/github.com/actions/workflow-parser/parser/parser.go
generated
vendored
Normal file
|
@ -0,0 +1,807 @@
|
|||
package parser
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"io"
|
||||
"io/ioutil"
|
||||
"regexp"
|
||||
"strings"
|
||||
|
||||
"github.com/actions/workflow-parser/model"
|
||||
"github.com/hashicorp/hcl"
|
||||
"github.com/hashicorp/hcl/hcl/ast"
|
||||
hclparser "github.com/hashicorp/hcl/hcl/parser"
|
||||
"github.com/hashicorp/hcl/hcl/token"
|
||||
"github.com/soniakeys/graph"
|
||||
)
|
||||
|
||||
const minVersion = 0
|
||||
const maxVersion = 0
|
||||
const maxSecrets = 100
|
||||
|
||||
type Parser struct {
|
||||
version int
|
||||
actions []*model.Action
|
||||
workflows []*model.Workflow
|
||||
errors errorList
|
||||
|
||||
posMap map[interface{}]ast.Node
|
||||
suppressSeverity Severity
|
||||
}
|
||||
|
||||
// Parse parses a .workflow file and return the actions and global variables found within.
|
||||
func Parse(reader io.Reader, options ...OptionFunc) (*model.Configuration, error) {
|
||||
// FIXME - check context for deadline?
|
||||
b, err := ioutil.ReadAll(reader)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
root, err := hcl.ParseBytes(b)
|
||||
if err != nil {
|
||||
if pe, ok := err.(*hclparser.PosError); ok {
|
||||
pos := ErrorPos{File: pe.Pos.Filename, Line: pe.Pos.Line, Column: pe.Pos.Column}
|
||||
errors := errorList{newFatal(pos, pe.Err.Error())}
|
||||
return nil, &Error{
|
||||
message: "unable to parse",
|
||||
Errors: errors,
|
||||
}
|
||||
}
|
||||
return nil, err
|
||||
}
|
||||
|
||||
p := parseAndValidate(root.Node, options...)
|
||||
if len(p.errors) > 0 {
|
||||
return nil, &Error{
|
||||
message: "unable to parse and validate",
|
||||
Errors: p.errors,
|
||||
Actions: p.actions,
|
||||
Workflows: p.workflows,
|
||||
}
|
||||
}
|
||||
|
||||
return &model.Configuration{
|
||||
Actions: p.actions,
|
||||
Workflows: p.workflows,
|
||||
}, nil
|
||||
}
|
||||
|
||||
// parseAndValidate converts a HCL AST into a Parser and validates
|
||||
// high-level structure.
|
||||
// Parameters:
|
||||
// - root - the contents of a .workflow file, as AST
|
||||
// Returns:
|
||||
// - a Parser structure containing actions and workflow definitions
|
||||
func parseAndValidate(root ast.Node, options ...OptionFunc) *Parser {
|
||||
p := &Parser{
|
||||
posMap: make(map[interface{}]ast.Node),
|
||||
}
|
||||
|
||||
for _, option := range options {
|
||||
option(p)
|
||||
}
|
||||
|
||||
p.parseRoot(root)
|
||||
p.validate()
|
||||
p.errors.sort()
|
||||
|
||||
return p
|
||||
}
|
||||
|
||||
func (p *Parser) validate() {
|
||||
p.analyzeDependencies()
|
||||
p.checkCircularDependencies()
|
||||
p.checkActions()
|
||||
p.checkFlows()
|
||||
}
|
||||
|
||||
func uniqStrings(items []string) []string {
|
||||
seen := make(map[string]bool)
|
||||
ret := make([]string, 0, len(items))
|
||||
for _, item := range items {
|
||||
if !seen[item] {
|
||||
seen[item] = true
|
||||
ret = append(ret, item)
|
||||
}
|
||||
}
|
||||
return ret
|
||||
}
|
||||
|
||||
// checkCircularDependencies finds loops in the action graph.
|
||||
// It emits a fatal error for each cycle it finds, in the order (top to
|
||||
// bottom, left to right) they appear in the .workflow file.
|
||||
func (p *Parser) checkCircularDependencies() {
|
||||
// make a map from action name to node ID, which is the index in the p.actions array
|
||||
// That is, p.actions[actionmap[X]].Identifier == X
|
||||
actionmap := make(map[string]graph.NI)
|
||||
for i, action := range p.actions {
|
||||
actionmap[action.Identifier] = graph.NI(i)
|
||||
}
|
||||
|
||||
// make an adjacency list representation of the action dependency graph
|
||||
adjList := make(graph.AdjacencyList, len(p.actions))
|
||||
for i, action := range p.actions {
|
||||
adjList[i] = make([]graph.NI, 0, len(action.Needs))
|
||||
for _, depName := range action.Needs {
|
||||
if depIdx, ok := actionmap[depName]; ok {
|
||||
adjList[i] = append(adjList[i], depIdx)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// find cycles, and print a fatal error for each one
|
||||
g := graph.Directed{AdjacencyList: adjList}
|
||||
g.Cycles(func(cycle []graph.NI) bool {
|
||||
node := p.posMap[&p.actions[cycle[len(cycle)-1]].Needs]
|
||||
p.addFatal(node, "Circular dependency on `%s'", p.actions[cycle[0]].Identifier)
|
||||
return true
|
||||
})
|
||||
}
|
||||
|
||||
// checkActions returns error if any actions are syntactically correct but
|
||||
// have structural errors
|
||||
func (p *Parser) checkActions() {
|
||||
secrets := make(map[string]bool)
|
||||
for _, t := range p.actions {
|
||||
// Ensure the Action has a `uses` attribute
|
||||
if t.Uses == nil {
|
||||
p.addError(p.posMap[t], "Action `%s' must have a `uses' attribute", t.Identifier)
|
||||
// continue, checking other actions
|
||||
}
|
||||
|
||||
// Ensure there aren't too many secrets
|
||||
for _, str := range t.Secrets {
|
||||
if !secrets[str] {
|
||||
secrets[str] = true
|
||||
if len(secrets) == maxSecrets+1 {
|
||||
p.addError(p.posMap[&t.Secrets], "All actions combined must not have more than %d unique secrets", maxSecrets)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Ensure that no environment variable or secret begins with
|
||||
// "GITHUB_", unless it's "GITHUB_TOKEN".
|
||||
// Also ensure that all environment variable names come from the legal
|
||||
// form for environment variable names.
|
||||
// Finally, ensure that the same key name isn't used more than once
|
||||
// between env and secrets, combined.
|
||||
for k := range t.Env {
|
||||
p.checkEnvironmentVariable(k, p.posMap[&t.Env])
|
||||
}
|
||||
secretVars := make(map[string]bool)
|
||||
for _, k := range t.Secrets {
|
||||
p.checkEnvironmentVariable(k, p.posMap[&t.Secrets])
|
||||
if _, found := t.Env[k]; found {
|
||||
p.addError(p.posMap[&t.Secrets], "Secret `%s' conflicts with an environment variable with the same name", k)
|
||||
}
|
||||
if secretVars[k] {
|
||||
p.addWarning(p.posMap[&t.Secrets], "Secret `%s' redefined", k)
|
||||
}
|
||||
secretVars[k] = true
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
var envVarChecker = regexp.MustCompile(`\A[A-Za-z_][A-Za-z_0-9]*\z`)
|
||||
|
||||
func (p *Parser) checkEnvironmentVariable(key string, node ast.Node) {
|
||||
if key != "GITHUB_TOKEN" && strings.HasPrefix(key, "GITHUB_") {
|
||||
p.addWarning(node, "Environment variables and secrets beginning with `GITHUB_' are reserved")
|
||||
}
|
||||
if !envVarChecker.MatchString(key) {
|
||||
p.addWarning(node, "Environment variables and secrets must contain only A-Z, a-z, 0-9, and _ characters, got `%s'", key)
|
||||
}
|
||||
}
|
||||
|
||||
// checkFlows appends an error if any workflows are syntactically correct but
|
||||
// have structural errors
|
||||
func (p *Parser) checkFlows() {
|
||||
actionmap := makeActionMap(p.actions)
|
||||
for _, f := range p.workflows {
|
||||
// make sure there's an `on` attribute
|
||||
if f.On == "" {
|
||||
p.addError(p.posMap[f], "Workflow `%s' must have an `on' attribute", f.Identifier)
|
||||
// continue, checking other workflows
|
||||
} else if !isAllowedEventType(f.On) {
|
||||
p.addError(p.posMap[&f.On], "Workflow `%s' has unknown `on' value `%s'", f.Identifier, f.On)
|
||||
// continue, checking other workflows
|
||||
}
|
||||
|
||||
// make sure that the actions that are resolved all exist
|
||||
for _, actionID := range f.Resolves {
|
||||
_, ok := actionmap[actionID]
|
||||
if !ok {
|
||||
p.addError(p.posMap[&f.Resolves], "Workflow `%s' resolves unknown action `%s'", f.Identifier, actionID)
|
||||
// continue, checking other workflows
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func makeActionMap(actions []*model.Action) map[string]*model.Action {
|
||||
actionmap := make(map[string]*model.Action)
|
||||
for _, action := range actions {
|
||||
actionmap[action.Identifier] = action
|
||||
}
|
||||
return actionmap
|
||||
}
|
||||
|
||||
// Fill in Action dependencies for all actions based on explicit dependencies
|
||||
// declarations.
|
||||
//
|
||||
// p.actions is an array of Action objects, as parsed. The Action objects in
|
||||
// this array are mutated, by setting Action.dependencies for each.
|
||||
func (p *Parser) analyzeDependencies() {
|
||||
actionmap := makeActionMap(p.actions)
|
||||
for _, action := range p.actions {
|
||||
// analyze explicit dependencies for each "needs" keyword
|
||||
p.analyzeNeeds(action, actionmap)
|
||||
}
|
||||
|
||||
// uniq all the dependencies lists
|
||||
for _, action := range p.actions {
|
||||
if len(action.Needs) >= 2 {
|
||||
action.Needs = uniqStrings(action.Needs)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func (p *Parser) analyzeNeeds(action *model.Action, actionmap map[string]*model.Action) {
|
||||
for _, need := range action.Needs {
|
||||
_, ok := actionmap[need]
|
||||
if !ok {
|
||||
p.addError(p.posMap[&action.Needs], "Action `%s' needs nonexistent action `%s'", action.Identifier, need)
|
||||
// continue, checking other actions
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// literalToStringMap converts a object value from the AST to a
|
||||
// map[string]string. For example, the HCL `{ a="b" c="d" }` becomes the
|
||||
// Go expression map[string]string{ "a": "b", "c": "d" }.
|
||||
// If the value doesn't adhere to that format -- e.g.,
|
||||
// if it's not an object, or it has non-assignment attributes, or if any
|
||||
// of its values are anything other than a string, the function appends an
|
||||
// appropriate error.
|
||||
func (p *Parser) literalToStringMap(node ast.Node) map[string]string {
|
||||
obj, ok := node.(*ast.ObjectType)
|
||||
|
||||
if !ok {
|
||||
p.addError(node, "Expected object, got %s", typename(node))
|
||||
return nil
|
||||
}
|
||||
|
||||
p.checkAssignmentsOnly(obj.List, "")
|
||||
|
||||
ret := make(map[string]string)
|
||||
for _, item := range obj.List.Items {
|
||||
if !isAssignment(item) {
|
||||
continue
|
||||
}
|
||||
str, ok := p.literalToString(item.Val)
|
||||
if ok {
|
||||
key := p.identString(item.Keys[0].Token)
|
||||
if key != "" {
|
||||
if _, found := ret[key]; found {
|
||||
p.addWarning(node, "Environment variable `%s' redefined", key)
|
||||
}
|
||||
ret[key] = str
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return ret
|
||||
}
|
||||
|
||||
func (p *Parser) identString(t token.Token) string {
|
||||
switch t.Type {
|
||||
case token.STRING:
|
||||
return t.Value().(string)
|
||||
case token.IDENT:
|
||||
return t.Text
|
||||
default:
|
||||
p.addErrorFromToken(t,
|
||||
"Each identifier should be a string, got %s",
|
||||
strings.ToLower(t.Type.String()))
|
||||
return ""
|
||||
}
|
||||
}
|
||||
|
||||
// literalToStringArray converts a list value from the AST to a []string.
|
||||
// For example, the HCL `[ "a", "b", "c" ]` becomes the Go expression
|
||||
// []string{ "a", "b", "c" }.
|
||||
// If the value doesn't adhere to that format -- it's not a list, or it
|
||||
// contains anything other than strings, the function appends an
|
||||
// appropriate error.
|
||||
// If promoteScalars is true, then values that are scalar strings are
|
||||
// promoted to a single-entry string array. E.g., "foo" becomes the Go
|
||||
// expression []string{ "foo" }.
|
||||
func (p *Parser) literalToStringArray(node ast.Node, promoteScalars bool) ([]string, bool) {
|
||||
literal, ok := node.(*ast.LiteralType)
|
||||
if ok {
|
||||
if promoteScalars && literal.Token.Type == token.STRING {
|
||||
return []string{literal.Token.Value().(string)}, true
|
||||
}
|
||||
p.addError(node, "Expected list, got %s", typename(node))
|
||||
return nil, false
|
||||
}
|
||||
|
||||
list, ok := node.(*ast.ListType)
|
||||
if !ok {
|
||||
p.addError(node, "Expected list, got %s", typename(node))
|
||||
return nil, false
|
||||
}
|
||||
|
||||
ret := make([]string, 0, len(list.List))
|
||||
for _, literal := range list.List {
|
||||
str, ok := p.literalToString(literal)
|
||||
if ok {
|
||||
ret = append(ret, str)
|
||||
}
|
||||
}
|
||||
|
||||
return ret, true
|
||||
}
|
||||
|
||||
// literalToString converts a literal value from the AST into a string.
|
||||
// If the value isn't a scalar or isn't a string, the function appends an
|
||||
// appropriate error and returns "", false.
|
||||
func (p *Parser) literalToString(node ast.Node) (string, bool) {
|
||||
val := p.literalCast(node, token.STRING)
|
||||
if val == nil {
|
||||
return "", false
|
||||
}
|
||||
return val.(string), true
|
||||
}
|
||||
|
||||
// literalToInt converts a literal value from the AST into an int64.
|
||||
// Supported number formats are: 123, 0x123, and 0123.
|
||||
// Exponents (1e6) and floats (123.456) generate errors.
|
||||
// If the value isn't a scalar or isn't a number, the function appends an
|
||||
// appropriate error and returns 0, false.
|
||||
func (p *Parser) literalToInt(node ast.Node) (int64, bool) {
|
||||
val := p.literalCast(node, token.NUMBER)
|
||||
if val == nil {
|
||||
return 0, false
|
||||
}
|
||||
return val.(int64), true
|
||||
}
|
||||
|
||||
func (p *Parser) literalCast(node ast.Node, t token.Type) interface{} {
|
||||
literal, ok := node.(*ast.LiteralType)
|
||||
if !ok {
|
||||
p.addError(node, "Expected %s, got %s", strings.ToLower(t.String()), typename(node))
|
||||
return nil
|
||||
}
|
||||
|
||||
if literal.Token.Type != t {
|
||||
p.addError(node, "Expected %s, got %s", strings.ToLower(t.String()), typename(node))
|
||||
return nil
|
||||
}
|
||||
|
||||
return literal.Token.Value()
|
||||
}
|
||||
|
||||
// parseRoot parses the root of the AST, filling in p.version, p.actions,
|
||||
// and p.workflows.
|
||||
func (p *Parser) parseRoot(node ast.Node) {
|
||||
objectList, ok := node.(*ast.ObjectList)
|
||||
if !ok {
|
||||
// It should be impossible for HCL to return anything other than an
|
||||
// ObjectList as the root node. This error should never happen.
|
||||
p.addError(node, "Internal error: root node must be an ObjectList")
|
||||
return
|
||||
}
|
||||
|
||||
p.actions = make([]*model.Action, 0, len(objectList.Items))
|
||||
p.workflows = make([]*model.Workflow, 0, len(objectList.Items))
|
||||
identifiers := make(map[string]bool)
|
||||
for idx, item := range objectList.Items {
|
||||
if item.Assign.IsValid() {
|
||||
p.parseVersion(idx, item)
|
||||
continue
|
||||
}
|
||||
p.parseBlock(item, identifiers)
|
||||
}
|
||||
}
|
||||
|
||||
// parseBlock parses a single, top-level "action" or "workflow" block,
|
||||
// appending it to p.actions or p.workflows as appropriate.
|
||||
func (p *Parser) parseBlock(item *ast.ObjectItem, identifiers map[string]bool) {
|
||||
if len(item.Keys) != 2 {
|
||||
p.addError(item, "Invalid toplevel declaration")
|
||||
return
|
||||
}
|
||||
|
||||
cmd := p.identString(item.Keys[0].Token)
|
||||
var id string
|
||||
|
||||
switch cmd {
|
||||
case "action":
|
||||
action := p.actionifyItem(item)
|
||||
if action != nil {
|
||||
id = action.Identifier
|
||||
p.actions = append(p.actions, action)
|
||||
}
|
||||
case "workflow":
|
||||
workflow := p.workflowifyItem(item)
|
||||
if workflow != nil {
|
||||
id = workflow.Identifier
|
||||
p.workflows = append(p.workflows, workflow)
|
||||
}
|
||||
default:
|
||||
p.addError(item, "Invalid toplevel keyword, `%s'", cmd)
|
||||
return
|
||||
}
|
||||
|
||||
if identifiers[id] {
|
||||
p.addError(item, "Identifier `%s' redefined", id)
|
||||
}
|
||||
|
||||
identifiers[id] = true
|
||||
}
|
||||
|
||||
// parseVersion parses a top-level `version=N` statement, filling in
|
||||
// p.version.
|
||||
func (p *Parser) parseVersion(idx int, item *ast.ObjectItem) {
|
||||
if len(item.Keys) != 1 || p.identString(item.Keys[0].Token) != "version" {
|
||||
// not a valid `version` declaration
|
||||
p.addError(item.Val, "Toplevel declarations cannot be assignments")
|
||||
return
|
||||
}
|
||||
if idx != 0 {
|
||||
p.addError(item.Val, "`version` must be the first declaration")
|
||||
return
|
||||
}
|
||||
version, ok := p.literalToInt(item.Val)
|
||||
if !ok {
|
||||
return
|
||||
}
|
||||
if version < minVersion || version > maxVersion {
|
||||
p.addError(item.Val, "`version = %d` is not supported", version)
|
||||
return
|
||||
}
|
||||
p.version = int(version)
|
||||
}
|
||||
|
||||
// parseIdentifier parses the double-quoted identifier (name) for a
|
||||
// "workflow" or "action" block.
|
||||
func (p *Parser) parseIdentifier(key *ast.ObjectKey) string {
|
||||
id := key.Token.Text
|
||||
if len(id) < 3 || id[0] != '"' || id[len(id)-1] != '"' {
|
||||
p.addError(key, "Invalid format for identifier `%s'", id)
|
||||
return ""
|
||||
}
|
||||
return id[1 : len(id)-1]
|
||||
}
|
||||
|
||||
// parseRequiredString parses a string value, setting its value into the
|
||||
// out-parameter `value` and returning true if successful.
|
||||
func (p *Parser) parseRequiredString(value *string, val ast.Node, nodeType, name, id string) bool {
|
||||
if *value != "" {
|
||||
p.addWarning(val, "`%s' redefined in %s `%s'", name, nodeType, id)
|
||||
// continue, allowing the redefinition
|
||||
}
|
||||
|
||||
newVal, ok := p.literalToString(val)
|
||||
if !ok {
|
||||
p.addError(val, "Invalid format for `%s' in %s `%s', expected string", name, nodeType, id)
|
||||
return false
|
||||
}
|
||||
|
||||
if newVal == "" {
|
||||
p.addError(val, "`%s' value in %s `%s' cannot be blank", name, nodeType, id)
|
||||
return false
|
||||
}
|
||||
|
||||
*value = newVal
|
||||
return true
|
||||
}
|
||||
|
||||
// parseBlockPreamble parses the beginning of a "workflow" or "action"
|
||||
// block.
|
||||
func (p *Parser) parseBlockPreamble(item *ast.ObjectItem, nodeType string) (string, *ast.ObjectType) {
|
||||
id := p.parseIdentifier(item.Keys[1])
|
||||
if id == "" {
|
||||
return "", nil
|
||||
}
|
||||
|
||||
node := item.Val
|
||||
obj, ok := node.(*ast.ObjectType)
|
||||
if !ok {
|
||||
p.addError(node, "Each %s must have an { ... } block", nodeType)
|
||||
return "", nil
|
||||
}
|
||||
|
||||
p.checkAssignmentsOnly(obj.List, id)
|
||||
|
||||
return id, obj
|
||||
}
|
||||
|
||||
// actionifyItem converts an AST block to an Action object.
|
||||
func (p *Parser) actionifyItem(item *ast.ObjectItem) *model.Action {
|
||||
id, obj := p.parseBlockPreamble(item, "action")
|
||||
if obj == nil {
|
||||
return nil
|
||||
}
|
||||
|
||||
action := &model.Action{
|
||||
Identifier: id,
|
||||
}
|
||||
p.posMap[action] = item
|
||||
|
||||
for _, item := range obj.List.Items {
|
||||
p.parseActionAttribute(p.identString(item.Keys[0].Token), action, item.Val)
|
||||
}
|
||||
|
||||
return action
|
||||
}
|
||||
|
||||
// parseActionAttribute parses a single key-value pair from an "action"
|
||||
// block. This function rejects any unknown keys and enforces formatting
|
||||
// requirements on all values.
|
||||
// It also has higher-than-normal cyclomatic complexity, so we ask the
|
||||
// gocyclo linter to ignore it.
|
||||
// nolint: gocyclo
|
||||
func (p *Parser) parseActionAttribute(name string, action *model.Action, val ast.Node) {
|
||||
switch name {
|
||||
case "uses":
|
||||
p.parseUses(action, val)
|
||||
case "needs":
|
||||
if needs, ok := p.literalToStringArray(val, true); ok {
|
||||
action.Needs = needs
|
||||
p.posMap[&action.Needs] = val
|
||||
}
|
||||
case "runs":
|
||||
if runs := p.parseCommand(action, action.Runs, name, val, false); runs != nil {
|
||||
action.Runs = runs
|
||||
}
|
||||
case "args":
|
||||
if args := p.parseCommand(action, action.Args, name, val, true); args != nil {
|
||||
action.Args = args
|
||||
}
|
||||
case "env":
|
||||
if env := p.literalToStringMap(val); env != nil {
|
||||
action.Env = env
|
||||
}
|
||||
p.posMap[&action.Env] = val
|
||||
case "secrets":
|
||||
if secrets, ok := p.literalToStringArray(val, false); ok {
|
||||
action.Secrets = secrets
|
||||
p.posMap[&action.Secrets] = val
|
||||
}
|
||||
default:
|
||||
p.addWarning(val, "Unknown action attribute `%s'", name)
|
||||
}
|
||||
}
|
||||
|
||||
// parseUses sets the action.Uses value based on the contents of the AST
|
||||
// node. This function enforces formatting requirements on the value.
|
||||
func (p *Parser) parseUses(action *model.Action, node ast.Node) {
|
||||
if action.Uses != nil {
|
||||
p.addWarning(node, "`uses' redefined in action `%s'", action.Identifier)
|
||||
// continue, allowing the redefinition
|
||||
}
|
||||
strVal, ok := p.literalToString(node)
|
||||
if !ok {
|
||||
return
|
||||
}
|
||||
|
||||
if strVal == "" {
|
||||
action.Uses = &model.UsesInvalid{}
|
||||
p.addError(node, "`uses' value in action `%s' cannot be blank", action.Identifier)
|
||||
return
|
||||
}
|
||||
if strings.HasPrefix(strVal, "./") {
|
||||
action.Uses = &model.UsesPath{Path: strings.TrimPrefix(strVal, "./")}
|
||||
return
|
||||
}
|
||||
|
||||
if strings.HasPrefix(strVal, "docker://") {
|
||||
action.Uses = &model.UsesDockerImage{Image: strings.TrimPrefix(strVal, "docker://")}
|
||||
return
|
||||
}
|
||||
|
||||
tok := strings.Split(strVal, "@")
|
||||
if len(tok) != 2 {
|
||||
action.Uses = &model.UsesInvalid{Raw: strVal}
|
||||
p.addError(node, "The `uses' attribute must be a path, a Docker image, or owner/repo@ref")
|
||||
return
|
||||
}
|
||||
ref := tok[1]
|
||||
tok = strings.SplitN(tok[0], "/", 3)
|
||||
if len(tok) < 2 {
|
||||
action.Uses = &model.UsesInvalid{Raw: strVal}
|
||||
p.addError(node, "The `uses' attribute must be a path, a Docker image, or owner/repo@ref")
|
||||
return
|
||||
}
|
||||
usesRepo := &model.UsesRepository{Repository: tok[0] + "/" + tok[1], Ref: ref}
|
||||
action.Uses = usesRepo
|
||||
if len(tok) == 3 {
|
||||
usesRepo.Path = tok[2]
|
||||
}
|
||||
}
|
||||
|
||||
// parseUses sets the action.Runs or action.Args value based on the
|
||||
// contents of the AST node. This function enforces formatting
|
||||
// requirements on the value.
|
||||
func (p *Parser) parseCommand(action *model.Action, cmd model.Command, name string, node ast.Node, allowBlank bool) model.Command {
|
||||
if cmd != nil {
|
||||
p.addWarning(node, "`%s' redefined in action `%s'", name, action.Identifier)
|
||||
// continue, allowing the redefinition
|
||||
}
|
||||
|
||||
// Is it a list?
|
||||
if _, ok := node.(*ast.ListType); ok {
|
||||
if parsed, ok := p.literalToStringArray(node, false); ok {
|
||||
return &model.ListCommand{Values: parsed}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// If not, parse a whitespace-separated string into a list.
|
||||
var raw string
|
||||
var ok bool
|
||||
if raw, ok = p.literalToString(node); !ok {
|
||||
p.addError(node, "The `%s' attribute must be a string or a list", name)
|
||||
return nil
|
||||
}
|
||||
if raw == "" && !allowBlank {
|
||||
p.addError(node, "`%s' value in action `%s' cannot be blank", name, action.Identifier)
|
||||
return nil
|
||||
}
|
||||
return &model.StringCommand{Value: raw}
|
||||
}
|
||||
|
||||
func typename(val interface{}) string {
|
||||
switch cast := val.(type) {
|
||||
case *ast.ListType:
|
||||
return "list"
|
||||
case *ast.LiteralType:
|
||||
return strings.ToLower(cast.Token.Type.String())
|
||||
case *ast.ObjectType:
|
||||
return "object"
|
||||
default:
|
||||
return fmt.Sprintf("%T", val)
|
||||
}
|
||||
}
|
||||
|
||||
// workflowifyItem converts an AST block to a Workflow object.
|
||||
func (p *Parser) workflowifyItem(item *ast.ObjectItem) *model.Workflow {
|
||||
id, obj := p.parseBlockPreamble(item, "workflow")
|
||||
if obj == nil {
|
||||
return nil
|
||||
}
|
||||
|
||||
var ok bool
|
||||
workflow := &model.Workflow{Identifier: id}
|
||||
for _, item := range obj.List.Items {
|
||||
name := p.identString(item.Keys[0].Token)
|
||||
|
||||
switch name {
|
||||
case "on":
|
||||
ok = p.parseRequiredString(&workflow.On, item.Val, "workflow", name, id)
|
||||
if ok {
|
||||
p.posMap[&workflow.On] = item
|
||||
}
|
||||
case "resolves":
|
||||
if workflow.Resolves != nil {
|
||||
p.addWarning(item.Val, "`resolves' redefined in workflow `%s'", id)
|
||||
// continue, allowing the redefinition
|
||||
}
|
||||
workflow.Resolves, ok = p.literalToStringArray(item.Val, true)
|
||||
p.posMap[&workflow.Resolves] = item
|
||||
if !ok {
|
||||
p.addError(item.Val, "Invalid format for `resolves' in workflow `%s', expected list of strings", id)
|
||||
// continue, allowing workflow with no `resolves`
|
||||
}
|
||||
default:
|
||||
p.addWarning(item.Val, "Unknown workflow attribute `%s'", name)
|
||||
// continue, treat as no-op
|
||||
}
|
||||
}
|
||||
|
||||
p.posMap[workflow] = item
|
||||
return workflow
|
||||
}
|
||||
|
||||
func isAssignment(item *ast.ObjectItem) bool {
|
||||
return len(item.Keys) == 1 && item.Assign.IsValid()
|
||||
}
|
||||
|
||||
// checkAssignmentsOnly ensures that all elements in the object are "key =
|
||||
// value" pairs.
|
||||
func (p *Parser) checkAssignmentsOnly(objectList *ast.ObjectList, actionID string) {
|
||||
for _, item := range objectList.Items {
|
||||
if !isAssignment(item) {
|
||||
var desc string
|
||||
if actionID == "" {
|
||||
desc = "the object"
|
||||
} else {
|
||||
desc = fmt.Sprintf("action `%s'", actionID)
|
||||
}
|
||||
p.addErrorFromObjectItem(item, "Each attribute of %s must be an assignment", desc)
|
||||
continue
|
||||
}
|
||||
|
||||
child, ok := item.Val.(*ast.ObjectType)
|
||||
if ok {
|
||||
p.checkAssignmentsOnly(child.List, actionID)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func (p *Parser) addWarning(node ast.Node, format string, a ...interface{}) {
|
||||
if p.suppressSeverity < WARNING {
|
||||
p.errors = append(p.errors, newWarning(posFromNode(node), format, a...))
|
||||
}
|
||||
}
|
||||
|
||||
func (p *Parser) addError(node ast.Node, format string, a ...interface{}) {
|
||||
if p.suppressSeverity < ERROR {
|
||||
p.errors = append(p.errors, newError(posFromNode(node), format, a...))
|
||||
}
|
||||
}
|
||||
|
||||
func (p *Parser) addErrorFromToken(t token.Token, format string, a ...interface{}) {
|
||||
if p.suppressSeverity < ERROR {
|
||||
p.errors = append(p.errors, newError(posFromToken(t), format, a...))
|
||||
}
|
||||
}
|
||||
|
||||
func (p *Parser) addErrorFromObjectItem(objectItem *ast.ObjectItem, format string, a ...interface{}) {
|
||||
if p.suppressSeverity < ERROR {
|
||||
p.errors = append(p.errors, newError(posFromObjectItem(objectItem), format, a...))
|
||||
}
|
||||
}
|
||||
|
||||
func (p *Parser) addFatal(node ast.Node, format string, a ...interface{}) {
|
||||
if p.suppressSeverity < FATAL {
|
||||
p.errors = append(p.errors, newFatal(posFromNode(node), format, a...))
|
||||
}
|
||||
}
|
||||
|
||||
// posFromNode returns an ErrorPos (file, line, and column) from an AST
|
||||
// node, so we can report specific locations for each parse error.
|
||||
func posFromNode(node ast.Node) ErrorPos {
|
||||
var pos *token.Pos
|
||||
switch cast := node.(type) {
|
||||
case *ast.ObjectList:
|
||||
if len(cast.Items) > 0 {
|
||||
if len(cast.Items[0].Keys) > 0 {
|
||||
pos = &cast.Items[0].Keys[0].Token.Pos
|
||||
}
|
||||
}
|
||||
case *ast.ObjectItem:
|
||||
return posFromNode(cast.Val)
|
||||
case *ast.ObjectType:
|
||||
pos = &cast.Lbrace
|
||||
case *ast.LiteralType:
|
||||
pos = &cast.Token.Pos
|
||||
case *ast.ListType:
|
||||
pos = &cast.Lbrack
|
||||
case *ast.ObjectKey:
|
||||
pos = &cast.Token.Pos
|
||||
}
|
||||
|
||||
if pos == nil {
|
||||
return ErrorPos{}
|
||||
}
|
||||
return ErrorPos{File: pos.Filename, Line: pos.Line, Column: pos.Column}
|
||||
}
|
||||
|
||||
// posFromObjectItem returns an ErrorPos from an ObjectItem. This is for
|
||||
// cases where posFromNode(item) would fail because the item has no Val
|
||||
// set.
|
||||
func posFromObjectItem(item *ast.ObjectItem) ErrorPos {
|
||||
if len(item.Keys) > 0 {
|
||||
return posFromNode(item.Keys[0])
|
||||
}
|
||||
return ErrorPos{}
|
||||
}
|
||||
|
||||
// posFromToken returns an ErrorPos from a Token. We can't use
|
||||
// posFromNode here because Tokens aren't Nodes.
|
||||
func posFromToken(token token.Token) ErrorPos {
|
||||
return ErrorPos{File: token.Pos.Filename, Line: token.Pos.Line, Column: token.Pos.Column}
|
||||
}
|
2
vendor/github.com/emirpasic/gods/containers/enumerable.go
generated
vendored
2
vendor/github.com/emirpasic/gods/containers/enumerable.go
generated
vendored
|
@ -11,7 +11,7 @@ type EnumerableWithIndex interface {
|
|||
|
||||
// Map invokes the given function once for each element and returns a
|
||||
// container containing the values returned by the given function.
|
||||
// TODO need help on how to enforce this in containers (don't want to type assert when chaining)
|
||||
// TODO would appreciate help on how to enforce this in containers (don't want to type assert when chaining)
|
||||
// Map(func(index int, value interface{}) interface{}) Container
|
||||
|
||||
// Select returns a new container containing all elements for which the given function returns a true value.
|
||||
|
|
52
vendor/github.com/emirpasic/gods/lists/arraylist/arraylist.go
generated
vendored
52
vendor/github.com/emirpasic/gods/lists/arraylist/arraylist.go
generated
vendored
|
@ -11,9 +11,10 @@ package arraylist
|
|||
|
||||
import (
|
||||
"fmt"
|
||||
"strings"
|
||||
|
||||
"github.com/emirpasic/gods/lists"
|
||||
"github.com/emirpasic/gods/utils"
|
||||
"strings"
|
||||
)
|
||||
|
||||
func assertListImplementation() {
|
||||
|
@ -31,9 +32,13 @@ const (
|
|||
shrinkFactor = float32(0.25) // shrink when size is 25% of capacity (0 means never shrink)
|
||||
)
|
||||
|
||||
// New instantiates a new empty list
|
||||
func New() *List {
|
||||
return &List{}
|
||||
// New instantiates a new list and adds the passed values, if any, to the list
|
||||
func New(values ...interface{}) *List {
|
||||
list := &List{}
|
||||
if len(values) > 0 {
|
||||
list.Add(values...)
|
||||
}
|
||||
return list
|
||||
}
|
||||
|
||||
// Add appends a value at the end of the list
|
||||
|
@ -56,7 +61,7 @@ func (list *List) Get(index int) (interface{}, bool) {
|
|||
return list.elements[index], true
|
||||
}
|
||||
|
||||
// Remove removes one or more elements from the list with the supplied indices.
|
||||
// Remove removes the element at the given index from the list.
|
||||
func (list *List) Remove(index int) {
|
||||
|
||||
if !list.withinRange(index) {
|
||||
|
@ -98,6 +103,19 @@ func (list *List) Values() []interface{} {
|
|||
return newElements
|
||||
}
|
||||
|
||||
//IndexOf returns index of provided element
|
||||
func (list *List) IndexOf(value interface{}) int {
|
||||
if list.size == 0 {
|
||||
return -1
|
||||
}
|
||||
for index, element := range list.elements {
|
||||
if element == value {
|
||||
return index
|
||||
}
|
||||
}
|
||||
return -1
|
||||
}
|
||||
|
||||
// Empty returns true if list does not contain any elements.
|
||||
func (list *List) Empty() bool {
|
||||
return list.size == 0
|
||||
|
@ -145,14 +163,24 @@ func (list *List) Insert(index int, values ...interface{}) {
|
|||
l := len(values)
|
||||
list.growBy(l)
|
||||
list.size += l
|
||||
// Shift old to right
|
||||
for i := list.size - 1; i >= index+l; i-- {
|
||||
list.elements[i] = list.elements[i-l]
|
||||
}
|
||||
// Insert new
|
||||
for i, value := range values {
|
||||
list.elements[index+i] = value
|
||||
copy(list.elements[index+l:], list.elements[index:list.size-l])
|
||||
copy(list.elements[index:], values)
|
||||
}
|
||||
|
||||
// Set the value at specified index
|
||||
// Does not do anything if position is negative or bigger than list's size
|
||||
// Note: position equal to list's size is valid, i.e. append.
|
||||
func (list *List) Set(index int, value interface{}) {
|
||||
|
||||
if !list.withinRange(index) {
|
||||
// Append
|
||||
if index == list.size {
|
||||
list.Add(value)
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
list.elements[index] = value
|
||||
}
|
||||
|
||||
// String returns a string representation of container
|
||||
|
|
1
vendor/github.com/emirpasic/gods/lists/lists.go
generated
vendored
1
vendor/github.com/emirpasic/gods/lists/lists.go
generated
vendored
|
@ -23,6 +23,7 @@ type List interface {
|
|||
Sort(comparator utils.Comparator)
|
||||
Swap(index1, index2 int)
|
||||
Insert(index int, values ...interface{})
|
||||
Set(index int, value interface{})
|
||||
|
||||
containers.Container
|
||||
// Empty() bool
|
||||
|
|
4
vendor/github.com/emirpasic/gods/trees/binaryheap/serialization.go
generated
vendored
4
vendor/github.com/emirpasic/gods/trees/binaryheap/serialization.go
generated
vendored
|
@ -11,12 +11,12 @@ func assertSerializationImplementation() {
|
|||
var _ containers.JSONDeserializer = (*Heap)(nil)
|
||||
}
|
||||
|
||||
// ToJSON outputs the JSON representation of list's elements.
|
||||
// ToJSON outputs the JSON representation of the heap.
|
||||
func (heap *Heap) ToJSON() ([]byte, error) {
|
||||
return heap.list.ToJSON()
|
||||
}
|
||||
|
||||
// FromJSON populates list's elements from the input JSON representation.
|
||||
// FromJSON populates the heap from the input JSON representation.
|
||||
func (heap *Heap) FromJSON(data []byte) error {
|
||||
return heap.list.FromJSON(data)
|
||||
}
|
||||
|
|
27
vendor/github.com/google/go-cmp/LICENSE
generated
vendored
Normal file
27
vendor/github.com/google/go-cmp/LICENSE
generated
vendored
Normal file
|
@ -0,0 +1,27 @@
|
|||
Copyright (c) 2017 The Go Authors. All rights reserved.
|
||||
|
||||
Redistribution and use in source and binary forms, with or without
|
||||
modification, are permitted provided that the following conditions are
|
||||
met:
|
||||
|
||||
* Redistributions of source code must retain the above copyright
|
||||
notice, this list of conditions and the following disclaimer.
|
||||
* Redistributions in binary form must reproduce the above
|
||||
copyright notice, this list of conditions and the following disclaimer
|
||||
in the documentation and/or other materials provided with the
|
||||
distribution.
|
||||
* Neither the name of Google Inc. nor the names of its
|
||||
contributors may be used to endorse or promote products derived from
|
||||
this software without specific prior written permission.
|
||||
|
||||
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
|
||||
"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
|
||||
LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
|
||||
A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
|
||||
OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
|
||||
SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
|
||||
LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
|
||||
DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
|
||||
THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
|
||||
(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
|
||||
OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
553
vendor/github.com/google/go-cmp/cmp/compare.go
generated
vendored
Normal file
553
vendor/github.com/google/go-cmp/cmp/compare.go
generated
vendored
Normal file
|
@ -0,0 +1,553 @@
|
|||
// Copyright 2017, The Go Authors. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE.md file.
|
||||
|
||||
// Package cmp determines equality of values.
|
||||
//
|
||||
// This package is intended to be a more powerful and safer alternative to
|
||||
// reflect.DeepEqual for comparing whether two values are semantically equal.
|
||||
//
|
||||
// The primary features of cmp are:
|
||||
//
|
||||
// • When the default behavior of equality does not suit the needs of the test,
|
||||
// custom equality functions can override the equality operation.
|
||||
// For example, an equality function may report floats as equal so long as they
|
||||
// are within some tolerance of each other.
|
||||
//
|
||||
// • Types that have an Equal method may use that method to determine equality.
|
||||
// This allows package authors to determine the equality operation for the types
|
||||
// that they define.
|
||||
//
|
||||
// • If no custom equality functions are used and no Equal method is defined,
|
||||
// equality is determined by recursively comparing the primitive kinds on both
|
||||
// values, much like reflect.DeepEqual. Unlike reflect.DeepEqual, unexported
|
||||
// fields are not compared by default; they result in panics unless suppressed
|
||||
// by using an Ignore option (see cmpopts.IgnoreUnexported) or explicitly compared
|
||||
// using the AllowUnexported option.
|
||||
package cmp
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"reflect"
|
||||
|
||||
"github.com/google/go-cmp/cmp/internal/diff"
|
||||
"github.com/google/go-cmp/cmp/internal/function"
|
||||
"github.com/google/go-cmp/cmp/internal/value"
|
||||
)
|
||||
|
||||
// BUG(dsnet): Maps with keys containing NaN values cannot be properly compared due to
|
||||
// the reflection package's inability to retrieve such entries. Equal will panic
|
||||
// anytime it comes across a NaN key, but this behavior may change.
|
||||
//
|
||||
// See https://golang.org/issue/11104 for more details.
|
||||
|
||||
var nothing = reflect.Value{}
|
||||
|
||||
// Equal reports whether x and y are equal by recursively applying the
|
||||
// following rules in the given order to x and y and all of their sub-values:
|
||||
//
|
||||
// • If two values are not of the same type, then they are never equal
|
||||
// and the overall result is false.
|
||||
//
|
||||
// • Let S be the set of all Ignore, Transformer, and Comparer options that
|
||||
// remain after applying all path filters, value filters, and type filters.
|
||||
// If at least one Ignore exists in S, then the comparison is ignored.
|
||||
// If the number of Transformer and Comparer options in S is greater than one,
|
||||
// then Equal panics because it is ambiguous which option to use.
|
||||
// If S contains a single Transformer, then use that to transform the current
|
||||
// values and recursively call Equal on the output values.
|
||||
// If S contains a single Comparer, then use that to compare the current values.
|
||||
// Otherwise, evaluation proceeds to the next rule.
|
||||
//
|
||||
// • If the values have an Equal method of the form "(T) Equal(T) bool" or
|
||||
// "(T) Equal(I) bool" where T is assignable to I, then use the result of
|
||||
// x.Equal(y) even if x or y is nil.
|
||||
// Otherwise, no such method exists and evaluation proceeds to the next rule.
|
||||
//
|
||||
// • Lastly, try to compare x and y based on their basic kinds.
|
||||
// Simple kinds like booleans, integers, floats, complex numbers, strings, and
|
||||
// channels are compared using the equivalent of the == operator in Go.
|
||||
// Functions are only equal if they are both nil, otherwise they are unequal.
|
||||
// Pointers are equal if the underlying values they point to are also equal.
|
||||
// Interfaces are equal if their underlying concrete values are also equal.
|
||||
//
|
||||
// Structs are equal if all of their fields are equal. If a struct contains
|
||||
// unexported fields, Equal panics unless the AllowUnexported option is used or
|
||||
// an Ignore option (e.g., cmpopts.IgnoreUnexported) ignores that field.
|
||||
//
|
||||
// Arrays, slices, and maps are equal if they are both nil or both non-nil
|
||||
// with the same length and the elements at each index or key are equal.
|
||||
// Note that a non-nil empty slice and a nil slice are not equal.
|
||||
// To equate empty slices and maps, consider using cmpopts.EquateEmpty.
|
||||
// Map keys are equal according to the == operator.
|
||||
// To use custom comparisons for map keys, consider using cmpopts.SortMaps.
|
||||
func Equal(x, y interface{}, opts ...Option) bool {
|
||||
s := newState(opts)
|
||||
s.compareAny(reflect.ValueOf(x), reflect.ValueOf(y))
|
||||
return s.result.Equal()
|
||||
}
|
||||
|
||||
// Diff returns a human-readable report of the differences between two values.
|
||||
// It returns an empty string if and only if Equal returns true for the same
|
||||
// input values and options. The output string will use the "-" symbol to
|
||||
// indicate elements removed from x, and the "+" symbol to indicate elements
|
||||
// added to y.
|
||||
//
|
||||
// Do not depend on this output being stable.
|
||||
func Diff(x, y interface{}, opts ...Option) string {
|
||||
r := new(defaultReporter)
|
||||
opts = Options{Options(opts), r}
|
||||
eq := Equal(x, y, opts...)
|
||||
d := r.String()
|
||||
if (d == "") != eq {
|
||||
panic("inconsistent difference and equality results")
|
||||
}
|
||||
return d
|
||||
}
|
||||
|
||||
type state struct {
|
||||
// These fields represent the "comparison state".
|
||||
// Calling statelessCompare must not result in observable changes to these.
|
||||
result diff.Result // The current result of comparison
|
||||
curPath Path // The current path in the value tree
|
||||
reporter reporter // Optional reporter used for difference formatting
|
||||
|
||||
// dynChecker triggers pseudo-random checks for option correctness.
|
||||
// It is safe for statelessCompare to mutate this value.
|
||||
dynChecker dynChecker
|
||||
|
||||
// These fields, once set by processOption, will not change.
|
||||
exporters map[reflect.Type]bool // Set of structs with unexported field visibility
|
||||
opts Options // List of all fundamental and filter options
|
||||
}
|
||||
|
||||
func newState(opts []Option) *state {
|
||||
s := new(state)
|
||||
for _, opt := range opts {
|
||||
s.processOption(opt)
|
||||
}
|
||||
return s
|
||||
}
|
||||
|
||||
func (s *state) processOption(opt Option) {
|
||||
switch opt := opt.(type) {
|
||||
case nil:
|
||||
case Options:
|
||||
for _, o := range opt {
|
||||
s.processOption(o)
|
||||
}
|
||||
case coreOption:
|
||||
type filtered interface {
|
||||
isFiltered() bool
|
||||
}
|
||||
if fopt, ok := opt.(filtered); ok && !fopt.isFiltered() {
|
||||
panic(fmt.Sprintf("cannot use an unfiltered option: %v", opt))
|
||||
}
|
||||
s.opts = append(s.opts, opt)
|
||||
case visibleStructs:
|
||||
if s.exporters == nil {
|
||||
s.exporters = make(map[reflect.Type]bool)
|
||||
}
|
||||
for t := range opt {
|
||||
s.exporters[t] = true
|
||||
}
|
||||
case reporter:
|
||||
if s.reporter != nil {
|
||||
panic("difference reporter already registered")
|
||||
}
|
||||
s.reporter = opt
|
||||
default:
|
||||
panic(fmt.Sprintf("unknown option %T", opt))
|
||||
}
|
||||
}
|
||||
|
||||
// statelessCompare compares two values and returns the result.
|
||||
// This function is stateless in that it does not alter the current result,
|
||||
// or output to any registered reporters.
|
||||
func (s *state) statelessCompare(vx, vy reflect.Value) diff.Result {
|
||||
// We do not save and restore the curPath because all of the compareX
|
||||
// methods should properly push and pop from the path.
|
||||
// It is an implementation bug if the contents of curPath differs from
|
||||
// when calling this function to when returning from it.
|
||||
|
||||
oldResult, oldReporter := s.result, s.reporter
|
||||
s.result = diff.Result{} // Reset result
|
||||
s.reporter = nil // Remove reporter to avoid spurious printouts
|
||||
s.compareAny(vx, vy)
|
||||
res := s.result
|
||||
s.result, s.reporter = oldResult, oldReporter
|
||||
return res
|
||||
}
|
||||
|
||||
func (s *state) compareAny(vx, vy reflect.Value) {
|
||||
// TODO: Support cyclic data structures.
|
||||
|
||||
// Rule 0: Differing types are never equal.
|
||||
if !vx.IsValid() || !vy.IsValid() {
|
||||
s.report(vx.IsValid() == vy.IsValid(), vx, vy)
|
||||
return
|
||||
}
|
||||
if vx.Type() != vy.Type() {
|
||||
s.report(false, vx, vy) // Possible for path to be empty
|
||||
return
|
||||
}
|
||||
t := vx.Type()
|
||||
if len(s.curPath) == 0 {
|
||||
s.curPath.push(&pathStep{typ: t})
|
||||
defer s.curPath.pop()
|
||||
}
|
||||
vx, vy = s.tryExporting(vx, vy)
|
||||
|
||||
// Rule 1: Check whether an option applies on this node in the value tree.
|
||||
if s.tryOptions(vx, vy, t) {
|
||||
return
|
||||
}
|
||||
|
||||
// Rule 2: Check whether the type has a valid Equal method.
|
||||
if s.tryMethod(vx, vy, t) {
|
||||
return
|
||||
}
|
||||
|
||||
// Rule 3: Recursively descend into each value's underlying kind.
|
||||
switch t.Kind() {
|
||||
case reflect.Bool:
|
||||
s.report(vx.Bool() == vy.Bool(), vx, vy)
|
||||
return
|
||||
case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64:
|
||||
s.report(vx.Int() == vy.Int(), vx, vy)
|
||||
return
|
||||
case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64, reflect.Uintptr:
|
||||
s.report(vx.Uint() == vy.Uint(), vx, vy)
|
||||
return
|
||||
case reflect.Float32, reflect.Float64:
|
||||
s.report(vx.Float() == vy.Float(), vx, vy)
|
||||
return
|
||||
case reflect.Complex64, reflect.Complex128:
|
||||
s.report(vx.Complex() == vy.Complex(), vx, vy)
|
||||
return
|
||||
case reflect.String:
|
||||
s.report(vx.String() == vy.String(), vx, vy)
|
||||
return
|
||||
case reflect.Chan, reflect.UnsafePointer:
|
||||
s.report(vx.Pointer() == vy.Pointer(), vx, vy)
|
||||
return
|
||||
case reflect.Func:
|
||||
s.report(vx.IsNil() && vy.IsNil(), vx, vy)
|
||||
return
|
||||
case reflect.Ptr:
|
||||
if vx.IsNil() || vy.IsNil() {
|
||||
s.report(vx.IsNil() && vy.IsNil(), vx, vy)
|
||||
return
|
||||
}
|
||||
s.curPath.push(&indirect{pathStep{t.Elem()}})
|
||||
defer s.curPath.pop()
|
||||
s.compareAny(vx.Elem(), vy.Elem())
|
||||
return
|
||||
case reflect.Interface:
|
||||
if vx.IsNil() || vy.IsNil() {
|
||||
s.report(vx.IsNil() && vy.IsNil(), vx, vy)
|
||||
return
|
||||
}
|
||||
if vx.Elem().Type() != vy.Elem().Type() {
|
||||
s.report(false, vx.Elem(), vy.Elem())
|
||||
return
|
||||
}
|
||||
s.curPath.push(&typeAssertion{pathStep{vx.Elem().Type()}})
|
||||
defer s.curPath.pop()
|
||||
s.compareAny(vx.Elem(), vy.Elem())
|
||||
return
|
||||
case reflect.Slice:
|
||||
if vx.IsNil() || vy.IsNil() {
|
||||
s.report(vx.IsNil() && vy.IsNil(), vx, vy)
|
||||
return
|
||||
}
|
||||
fallthrough
|
||||
case reflect.Array:
|
||||
s.compareArray(vx, vy, t)
|
||||
return
|
||||
case reflect.Map:
|
||||
s.compareMap(vx, vy, t)
|
||||
return
|
||||
case reflect.Struct:
|
||||
s.compareStruct(vx, vy, t)
|
||||
return
|
||||
default:
|
||||
panic(fmt.Sprintf("%v kind not handled", t.Kind()))
|
||||
}
|
||||
}
|
||||
|
||||
func (s *state) tryExporting(vx, vy reflect.Value) (reflect.Value, reflect.Value) {
|
||||
if sf, ok := s.curPath[len(s.curPath)-1].(*structField); ok && sf.unexported {
|
||||
if sf.force {
|
||||
// Use unsafe pointer arithmetic to get read-write access to an
|
||||
// unexported field in the struct.
|
||||
vx = unsafeRetrieveField(sf.pvx, sf.field)
|
||||
vy = unsafeRetrieveField(sf.pvy, sf.field)
|
||||
} else {
|
||||
// We are not allowed to export the value, so invalidate them
|
||||
// so that tryOptions can panic later if not explicitly ignored.
|
||||
vx = nothing
|
||||
vy = nothing
|
||||
}
|
||||
}
|
||||
return vx, vy
|
||||
}
|
||||
|
||||
func (s *state) tryOptions(vx, vy reflect.Value, t reflect.Type) bool {
|
||||
// If there were no FilterValues, we will not detect invalid inputs,
|
||||
// so manually check for them and append invalid if necessary.
|
||||
// We still evaluate the options since an ignore can override invalid.
|
||||
opts := s.opts
|
||||
if !vx.IsValid() || !vy.IsValid() {
|
||||
opts = Options{opts, invalid{}}
|
||||
}
|
||||
|
||||
// Evaluate all filters and apply the remaining options.
|
||||
if opt := opts.filter(s, vx, vy, t); opt != nil {
|
||||
opt.apply(s, vx, vy)
|
||||
return true
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
func (s *state) tryMethod(vx, vy reflect.Value, t reflect.Type) bool {
|
||||
// Check if this type even has an Equal method.
|
||||
m, ok := t.MethodByName("Equal")
|
||||
if !ok || !function.IsType(m.Type, function.EqualAssignable) {
|
||||
return false
|
||||
}
|
||||
|
||||
eq := s.callTTBFunc(m.Func, vx, vy)
|
||||
s.report(eq, vx, vy)
|
||||
return true
|
||||
}
|
||||
|
||||
func (s *state) callTRFunc(f, v reflect.Value) reflect.Value {
|
||||
v = sanitizeValue(v, f.Type().In(0))
|
||||
if !s.dynChecker.Next() {
|
||||
return f.Call([]reflect.Value{v})[0]
|
||||
}
|
||||
|
||||
// Run the function twice and ensure that we get the same results back.
|
||||
// We run in goroutines so that the race detector (if enabled) can detect
|
||||
// unsafe mutations to the input.
|
||||
c := make(chan reflect.Value)
|
||||
go detectRaces(c, f, v)
|
||||
want := f.Call([]reflect.Value{v})[0]
|
||||
if got := <-c; !s.statelessCompare(got, want).Equal() {
|
||||
// To avoid false-positives with non-reflexive equality operations,
|
||||
// we sanity check whether a value is equal to itself.
|
||||
if !s.statelessCompare(want, want).Equal() {
|
||||
return want
|
||||
}
|
||||
fn := getFuncName(f.Pointer())
|
||||
panic(fmt.Sprintf("non-deterministic function detected: %s", fn))
|
||||
}
|
||||
return want
|
||||
}
|
||||
|
||||
func (s *state) callTTBFunc(f, x, y reflect.Value) bool {
|
||||
x = sanitizeValue(x, f.Type().In(0))
|
||||
y = sanitizeValue(y, f.Type().In(1))
|
||||
if !s.dynChecker.Next() {
|
||||
return f.Call([]reflect.Value{x, y})[0].Bool()
|
||||
}
|
||||
|
||||
// Swapping the input arguments is sufficient to check that
|
||||
// f is symmetric and deterministic.
|
||||
// We run in goroutines so that the race detector (if enabled) can detect
|
||||
// unsafe mutations to the input.
|
||||
c := make(chan reflect.Value)
|
||||
go detectRaces(c, f, y, x)
|
||||
want := f.Call([]reflect.Value{x, y})[0].Bool()
|
||||
if got := <-c; !got.IsValid() || got.Bool() != want {
|
||||
fn := getFuncName(f.Pointer())
|
||||
panic(fmt.Sprintf("non-deterministic or non-symmetric function detected: %s", fn))
|
||||
}
|
||||
return want
|
||||
}
|
||||
|
||||
func detectRaces(c chan<- reflect.Value, f reflect.Value, vs ...reflect.Value) {
|
||||
var ret reflect.Value
|
||||
defer func() {
|
||||
recover() // Ignore panics, let the other call to f panic instead
|
||||
c <- ret
|
||||
}()
|
||||
ret = f.Call(vs)[0]
|
||||
}
|
||||
|
||||
// sanitizeValue converts nil interfaces of type T to those of type R,
|
||||
// assuming that T is assignable to R.
|
||||
// Otherwise, it returns the input value as is.
|
||||
func sanitizeValue(v reflect.Value, t reflect.Type) reflect.Value {
|
||||
// TODO(dsnet): Remove this hacky workaround.
|
||||
// See https://golang.org/issue/22143
|
||||
if v.Kind() == reflect.Interface && v.IsNil() && v.Type() != t {
|
||||
return reflect.New(t).Elem()
|
||||
}
|
||||
return v
|
||||
}
|
||||
|
||||
func (s *state) compareArray(vx, vy reflect.Value, t reflect.Type) {
|
||||
step := &sliceIndex{pathStep{t.Elem()}, 0, 0}
|
||||
s.curPath.push(step)
|
||||
|
||||
// Compute an edit-script for slices vx and vy.
|
||||
es := diff.Difference(vx.Len(), vy.Len(), func(ix, iy int) diff.Result {
|
||||
step.xkey, step.ykey = ix, iy
|
||||
return s.statelessCompare(vx.Index(ix), vy.Index(iy))
|
||||
})
|
||||
|
||||
// Report the entire slice as is if the arrays are of primitive kind,
|
||||
// and the arrays are different enough.
|
||||
isPrimitive := false
|
||||
switch t.Elem().Kind() {
|
||||
case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64,
|
||||
reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64, reflect.Uintptr,
|
||||
reflect.Bool, reflect.Float32, reflect.Float64, reflect.Complex64, reflect.Complex128:
|
||||
isPrimitive = true
|
||||
}
|
||||
if isPrimitive && es.Dist() > (vx.Len()+vy.Len())/4 {
|
||||
s.curPath.pop() // Pop first since we are reporting the whole slice
|
||||
s.report(false, vx, vy)
|
||||
return
|
||||
}
|
||||
|
||||
// Replay the edit-script.
|
||||
var ix, iy int
|
||||
for _, e := range es {
|
||||
switch e {
|
||||
case diff.UniqueX:
|
||||
step.xkey, step.ykey = ix, -1
|
||||
s.report(false, vx.Index(ix), nothing)
|
||||
ix++
|
||||
case diff.UniqueY:
|
||||
step.xkey, step.ykey = -1, iy
|
||||
s.report(false, nothing, vy.Index(iy))
|
||||
iy++
|
||||
default:
|
||||
step.xkey, step.ykey = ix, iy
|
||||
if e == diff.Identity {
|
||||
s.report(true, vx.Index(ix), vy.Index(iy))
|
||||
} else {
|
||||
s.compareAny(vx.Index(ix), vy.Index(iy))
|
||||
}
|
||||
ix++
|
||||
iy++
|
||||
}
|
||||
}
|
||||
s.curPath.pop()
|
||||
return
|
||||
}
|
||||
|
||||
func (s *state) compareMap(vx, vy reflect.Value, t reflect.Type) {
|
||||
if vx.IsNil() || vy.IsNil() {
|
||||
s.report(vx.IsNil() && vy.IsNil(), vx, vy)
|
||||
return
|
||||
}
|
||||
|
||||
// We combine and sort the two map keys so that we can perform the
|
||||
// comparisons in a deterministic order.
|
||||
step := &mapIndex{pathStep: pathStep{t.Elem()}}
|
||||
s.curPath.push(step)
|
||||
defer s.curPath.pop()
|
||||
for _, k := range value.SortKeys(append(vx.MapKeys(), vy.MapKeys()...)) {
|
||||
step.key = k
|
||||
vvx := vx.MapIndex(k)
|
||||
vvy := vy.MapIndex(k)
|
||||
switch {
|
||||
case vvx.IsValid() && vvy.IsValid():
|
||||
s.compareAny(vvx, vvy)
|
||||
case vvx.IsValid() && !vvy.IsValid():
|
||||
s.report(false, vvx, nothing)
|
||||
case !vvx.IsValid() && vvy.IsValid():
|
||||
s.report(false, nothing, vvy)
|
||||
default:
|
||||
// It is possible for both vvx and vvy to be invalid if the
|
||||
// key contained a NaN value in it. There is no way in
|
||||
// reflection to be able to retrieve these values.
|
||||
// See https://golang.org/issue/11104
|
||||
panic(fmt.Sprintf("%#v has map key with NaNs", s.curPath))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func (s *state) compareStruct(vx, vy reflect.Value, t reflect.Type) {
|
||||
var vax, vay reflect.Value // Addressable versions of vx and vy
|
||||
|
||||
step := &structField{}
|
||||
s.curPath.push(step)
|
||||
defer s.curPath.pop()
|
||||
for i := 0; i < t.NumField(); i++ {
|
||||
vvx := vx.Field(i)
|
||||
vvy := vy.Field(i)
|
||||
step.typ = t.Field(i).Type
|
||||
step.name = t.Field(i).Name
|
||||
step.idx = i
|
||||
step.unexported = !isExported(step.name)
|
||||
if step.unexported {
|
||||
// Defer checking of unexported fields until later to give an
|
||||
// Ignore a chance to ignore the field.
|
||||
if !vax.IsValid() || !vay.IsValid() {
|
||||
// For unsafeRetrieveField to work, the parent struct must
|
||||
// be addressable. Create a new copy of the values if
|
||||
// necessary to make them addressable.
|
||||
vax = makeAddressable(vx)
|
||||
vay = makeAddressable(vy)
|
||||
}
|
||||
step.force = s.exporters[t]
|
||||
step.pvx = vax
|
||||
step.pvy = vay
|
||||
step.field = t.Field(i)
|
||||
}
|
||||
s.compareAny(vvx, vvy)
|
||||
}
|
||||
}
|
||||
|
||||
// report records the result of a single comparison.
|
||||
// It also calls Report if any reporter is registered.
|
||||
func (s *state) report(eq bool, vx, vy reflect.Value) {
|
||||
if eq {
|
||||
s.result.NSame++
|
||||
} else {
|
||||
s.result.NDiff++
|
||||
}
|
||||
if s.reporter != nil {
|
||||
s.reporter.Report(vx, vy, eq, s.curPath)
|
||||
}
|
||||
}
|
||||
|
||||
// dynChecker tracks the state needed to periodically perform checks that
|
||||
// user provided functions are symmetric and deterministic.
|
||||
// The zero value is safe for immediate use.
|
||||
type dynChecker struct{ curr, next int }
|
||||
|
||||
// Next increments the state and reports whether a check should be performed.
|
||||
//
|
||||
// Checks occur every Nth function call, where N is a triangular number:
|
||||
// 0 1 3 6 10 15 21 28 36 45 55 66 78 91 105 120 136 153 171 190 ...
|
||||
// See https://en.wikipedia.org/wiki/Triangular_number
|
||||
//
|
||||
// This sequence ensures that the cost of checks drops significantly as
|
||||
// the number of functions calls grows larger.
|
||||
func (dc *dynChecker) Next() bool {
|
||||
ok := dc.curr == dc.next
|
||||
if ok {
|
||||
dc.curr = 0
|
||||
dc.next++
|
||||
}
|
||||
dc.curr++
|
||||
return ok
|
||||
}
|
||||
|
||||
// makeAddressable returns a value that is always addressable.
|
||||
// It returns the input verbatim if it is already addressable,
|
||||
// otherwise it creates a new value and returns an addressable copy.
|
||||
func makeAddressable(v reflect.Value) reflect.Value {
|
||||
if v.CanAddr() {
|
||||
return v
|
||||
}
|
||||
vc := reflect.New(v.Type()).Elem()
|
||||
vc.Set(v)
|
||||
return vc
|
||||
}
|
17
vendor/github.com/google/go-cmp/cmp/internal/diff/debug_disable.go
generated
vendored
Normal file
17
vendor/github.com/google/go-cmp/cmp/internal/diff/debug_disable.go
generated
vendored
Normal file
|
@ -0,0 +1,17 @@
|
|||
// Copyright 2017, The Go Authors. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE.md file.
|
||||
|
||||
// +build !debug
|
||||
|
||||
package diff
|
||||
|
||||
var debug debugger
|
||||
|
||||
type debugger struct{}
|
||||
|
||||
func (debugger) Begin(_, _ int, f EqualFunc, _, _ *EditScript) EqualFunc {
|
||||
return f
|
||||
}
|
||||
func (debugger) Update() {}
|
||||
func (debugger) Finish() {}
|
122
vendor/github.com/google/go-cmp/cmp/internal/diff/debug_enable.go
generated
vendored
Normal file
122
vendor/github.com/google/go-cmp/cmp/internal/diff/debug_enable.go
generated
vendored
Normal file
|
@ -0,0 +1,122 @@
|
|||
// Copyright 2017, The Go Authors. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE.md file.
|
||||
|
||||
// +build debug
|
||||
|
||||
package diff
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"strings"
|
||||
"sync"
|
||||
"time"
|
||||
)
|
||||
|
||||
// The algorithm can be seen running in real-time by enabling debugging:
|
||||
// go test -tags=debug -v
|
||||
//
|
||||
// Example output:
|
||||
// === RUN TestDifference/#34
|
||||
// ┌───────────────────────────────┐
|
||||
// │ \ · · · · · · · · · · · · · · │
|
||||
// │ · # · · · · · · · · · · · · · │
|
||||
// │ · \ · · · · · · · · · · · · · │
|
||||
// │ · · \ · · · · · · · · · · · · │
|
||||
// │ · · · X # · · · · · · · · · · │
|
||||
// │ · · · # \ · · · · · · · · · · │
|
||||
// │ · · · · · # # · · · · · · · · │
|
||||
// │ · · · · · # \ · · · · · · · · │
|
||||
// │ · · · · · · · \ · · · · · · · │
|
||||
// │ · · · · · · · · \ · · · · · · │
|
||||
// │ · · · · · · · · · \ · · · · · │
|
||||
// │ · · · · · · · · · · \ · · # · │
|
||||
// │ · · · · · · · · · · · \ # # · │
|
||||
// │ · · · · · · · · · · · # # # · │
|
||||
// │ · · · · · · · · · · # # # # · │
|
||||
// │ · · · · · · · · · # # # # # · │
|
||||
// │ · · · · · · · · · · · · · · \ │
|
||||
// └───────────────────────────────┘
|
||||
// [.Y..M.XY......YXYXY.|]
|
||||
//
|
||||
// The grid represents the edit-graph where the horizontal axis represents
|
||||
// list X and the vertical axis represents list Y. The start of the two lists
|
||||
// is the top-left, while the ends are the bottom-right. The '·' represents
|
||||
// an unexplored node in the graph. The '\' indicates that the two symbols
|
||||
// from list X and Y are equal. The 'X' indicates that two symbols are similar
|
||||
// (but not exactly equal) to each other. The '#' indicates that the two symbols
|
||||
// are different (and not similar). The algorithm traverses this graph trying to
|
||||
// make the paths starting in the top-left and the bottom-right connect.
|
||||
//
|
||||
// The series of '.', 'X', 'Y', and 'M' characters at the bottom represents
|
||||
// the currently established path from the forward and reverse searches,
|
||||
// separated by a '|' character.
|
||||
|
||||
const (
|
||||
updateDelay = 100 * time.Millisecond
|
||||
finishDelay = 500 * time.Millisecond
|
||||
ansiTerminal = true // ANSI escape codes used to move terminal cursor
|
||||
)
|
||||
|
||||
var debug debugger
|
||||
|
||||
type debugger struct {
|
||||
sync.Mutex
|
||||
p1, p2 EditScript
|
||||
fwdPath, revPath *EditScript
|
||||
grid []byte
|
||||
lines int
|
||||
}
|
||||
|
||||
func (dbg *debugger) Begin(nx, ny int, f EqualFunc, p1, p2 *EditScript) EqualFunc {
|
||||
dbg.Lock()
|
||||
dbg.fwdPath, dbg.revPath = p1, p2
|
||||
top := "┌─" + strings.Repeat("──", nx) + "┐\n"
|
||||
row := "│ " + strings.Repeat("· ", nx) + "│\n"
|
||||
btm := "└─" + strings.Repeat("──", nx) + "┘\n"
|
||||
dbg.grid = []byte(top + strings.Repeat(row, ny) + btm)
|
||||
dbg.lines = strings.Count(dbg.String(), "\n")
|
||||
fmt.Print(dbg)
|
||||
|
||||
// Wrap the EqualFunc so that we can intercept each result.
|
||||
return func(ix, iy int) (r Result) {
|
||||
cell := dbg.grid[len(top)+iy*len(row):][len("│ ")+len("· ")*ix:][:len("·")]
|
||||
for i := range cell {
|
||||
cell[i] = 0 // Zero out the multiple bytes of UTF-8 middle-dot
|
||||
}
|
||||
switch r = f(ix, iy); {
|
||||
case r.Equal():
|
||||
cell[0] = '\\'
|
||||
case r.Similar():
|
||||
cell[0] = 'X'
|
||||
default:
|
||||
cell[0] = '#'
|
||||
}
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
func (dbg *debugger) Update() {
|
||||
dbg.print(updateDelay)
|
||||
}
|
||||
|
||||
func (dbg *debugger) Finish() {
|
||||
dbg.print(finishDelay)
|
||||
dbg.Unlock()
|
||||
}
|
||||
|
||||
func (dbg *debugger) String() string {
|
||||
dbg.p1, dbg.p2 = *dbg.fwdPath, dbg.p2[:0]
|
||||
for i := len(*dbg.revPath) - 1; i >= 0; i-- {
|
||||
dbg.p2 = append(dbg.p2, (*dbg.revPath)[i])
|
||||
}
|
||||
return fmt.Sprintf("%s[%v|%v]\n\n", dbg.grid, dbg.p1, dbg.p2)
|
||||
}
|
||||
|
||||
func (dbg *debugger) print(d time.Duration) {
|
||||
if ansiTerminal {
|
||||
fmt.Printf("\x1b[%dA", dbg.lines) // Reset terminal cursor
|
||||
}
|
||||
fmt.Print(dbg)
|
||||
time.Sleep(d)
|
||||
}
|
363
vendor/github.com/google/go-cmp/cmp/internal/diff/diff.go
generated
vendored
Normal file
363
vendor/github.com/google/go-cmp/cmp/internal/diff/diff.go
generated
vendored
Normal file
|
@ -0,0 +1,363 @@
|
|||
// Copyright 2017, The Go Authors. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE.md file.
|
||||
|
||||
// Package diff implements an algorithm for producing edit-scripts.
|
||||
// The edit-script is a sequence of operations needed to transform one list
|
||||
// of symbols into another (or vice-versa). The edits allowed are insertions,
|
||||
// deletions, and modifications. The summation of all edits is called the
|
||||
// Levenshtein distance as this problem is well-known in computer science.
|
||||
//
|
||||
// This package prioritizes performance over accuracy. That is, the run time
|
||||
// is more important than obtaining a minimal Levenshtein distance.
|
||||
package diff
|
||||
|
||||
// EditType represents a single operation within an edit-script.
|
||||
type EditType uint8
|
||||
|
||||
const (
|
||||
// Identity indicates that a symbol pair is identical in both list X and Y.
|
||||
Identity EditType = iota
|
||||
// UniqueX indicates that a symbol only exists in X and not Y.
|
||||
UniqueX
|
||||
// UniqueY indicates that a symbol only exists in Y and not X.
|
||||
UniqueY
|
||||
// Modified indicates that a symbol pair is a modification of each other.
|
||||
Modified
|
||||
)
|
||||
|
||||
// EditScript represents the series of differences between two lists.
|
||||
type EditScript []EditType
|
||||
|
||||
// String returns a human-readable string representing the edit-script where
|
||||
// Identity, UniqueX, UniqueY, and Modified are represented by the
|
||||
// '.', 'X', 'Y', and 'M' characters, respectively.
|
||||
func (es EditScript) String() string {
|
||||
b := make([]byte, len(es))
|
||||
for i, e := range es {
|
||||
switch e {
|
||||
case Identity:
|
||||
b[i] = '.'
|
||||
case UniqueX:
|
||||
b[i] = 'X'
|
||||
case UniqueY:
|
||||
b[i] = 'Y'
|
||||
case Modified:
|
||||
b[i] = 'M'
|
||||
default:
|
||||
panic("invalid edit-type")
|
||||
}
|
||||
}
|
||||
return string(b)
|
||||
}
|
||||
|
||||
// stats returns a histogram of the number of each type of edit operation.
|
||||
func (es EditScript) stats() (s struct{ NI, NX, NY, NM int }) {
|
||||
for _, e := range es {
|
||||
switch e {
|
||||
case Identity:
|
||||
s.NI++
|
||||
case UniqueX:
|
||||
s.NX++
|
||||
case UniqueY:
|
||||
s.NY++
|
||||
case Modified:
|
||||
s.NM++
|
||||
default:
|
||||
panic("invalid edit-type")
|
||||
}
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
// Dist is the Levenshtein distance and is guaranteed to be 0 if and only if
|
||||
// lists X and Y are equal.
|
||||
func (es EditScript) Dist() int { return len(es) - es.stats().NI }
|
||||
|
||||
// LenX is the length of the X list.
|
||||
func (es EditScript) LenX() int { return len(es) - es.stats().NY }
|
||||
|
||||
// LenY is the length of the Y list.
|
||||
func (es EditScript) LenY() int { return len(es) - es.stats().NX }
|
||||
|
||||
// EqualFunc reports whether the symbols at indexes ix and iy are equal.
|
||||
// When called by Difference, the index is guaranteed to be within nx and ny.
|
||||
type EqualFunc func(ix int, iy int) Result
|
||||
|
||||
// Result is the result of comparison.
|
||||
// NSame is the number of sub-elements that are equal.
|
||||
// NDiff is the number of sub-elements that are not equal.
|
||||
type Result struct{ NSame, NDiff int }
|
||||
|
||||
// Equal indicates whether the symbols are equal. Two symbols are equal
|
||||
// if and only if NDiff == 0. If Equal, then they are also Similar.
|
||||
func (r Result) Equal() bool { return r.NDiff == 0 }
|
||||
|
||||
// Similar indicates whether two symbols are similar and may be represented
|
||||
// by using the Modified type. As a special case, we consider binary comparisons
|
||||
// (i.e., those that return Result{1, 0} or Result{0, 1}) to be similar.
|
||||
//
|
||||
// The exact ratio of NSame to NDiff to determine similarity may change.
|
||||
func (r Result) Similar() bool {
|
||||
// Use NSame+1 to offset NSame so that binary comparisons are similar.
|
||||
return r.NSame+1 >= r.NDiff
|
||||
}
|
||||
|
||||
// Difference reports whether two lists of lengths nx and ny are equal
|
||||
// given the definition of equality provided as f.
|
||||
//
|
||||
// This function returns an edit-script, which is a sequence of operations
|
||||
// needed to convert one list into the other. The following invariants for
|
||||
// the edit-script are maintained:
|
||||
// • eq == (es.Dist()==0)
|
||||
// • nx == es.LenX()
|
||||
// • ny == es.LenY()
|
||||
//
|
||||
// This algorithm is not guaranteed to be an optimal solution (i.e., one that
|
||||
// produces an edit-script with a minimal Levenshtein distance). This algorithm
|
||||
// favors performance over optimality. The exact output is not guaranteed to
|
||||
// be stable and may change over time.
|
||||
func Difference(nx, ny int, f EqualFunc) (es EditScript) {
|
||||
// This algorithm is based on traversing what is known as an "edit-graph".
|
||||
// See Figure 1 from "An O(ND) Difference Algorithm and Its Variations"
|
||||
// by Eugene W. Myers. Since D can be as large as N itself, this is
|
||||
// effectively O(N^2). Unlike the algorithm from that paper, we are not
|
||||
// interested in the optimal path, but at least some "decent" path.
|
||||
//
|
||||
// For example, let X and Y be lists of symbols:
|
||||
// X = [A B C A B B A]
|
||||
// Y = [C B A B A C]
|
||||
//
|
||||
// The edit-graph can be drawn as the following:
|
||||
// A B C A B B A
|
||||
// ┌─────────────┐
|
||||
// C │_|_|\|_|_|_|_│ 0
|
||||
// B │_|\|_|_|\|\|_│ 1
|
||||
// A │\|_|_|\|_|_|\│ 2
|
||||
// B │_|\|_|_|\|\|_│ 3
|
||||
// A │\|_|_|\|_|_|\│ 4
|
||||
// C │ | |\| | | | │ 5
|
||||
// └─────────────┘ 6
|
||||
// 0 1 2 3 4 5 6 7
|
||||
//
|
||||
// List X is written along the horizontal axis, while list Y is written
|
||||
// along the vertical axis. At any point on this grid, if the symbol in
|
||||
// list X matches the corresponding symbol in list Y, then a '\' is drawn.
|
||||
// The goal of any minimal edit-script algorithm is to find a path from the
|
||||
// top-left corner to the bottom-right corner, while traveling through the
|
||||
// fewest horizontal or vertical edges.
|
||||
// A horizontal edge is equivalent to inserting a symbol from list X.
|
||||
// A vertical edge is equivalent to inserting a symbol from list Y.
|
||||
// A diagonal edge is equivalent to a matching symbol between both X and Y.
|
||||
|
||||
// Invariants:
|
||||
// • 0 ≤ fwdPath.X ≤ (fwdFrontier.X, revFrontier.X) ≤ revPath.X ≤ nx
|
||||
// • 0 ≤ fwdPath.Y ≤ (fwdFrontier.Y, revFrontier.Y) ≤ revPath.Y ≤ ny
|
||||
//
|
||||
// In general:
|
||||
// • fwdFrontier.X < revFrontier.X
|
||||
// • fwdFrontier.Y < revFrontier.Y
|
||||
// Unless, it is time for the algorithm to terminate.
|
||||
fwdPath := path{+1, point{0, 0}, make(EditScript, 0, (nx+ny)/2)}
|
||||
revPath := path{-1, point{nx, ny}, make(EditScript, 0)}
|
||||
fwdFrontier := fwdPath.point // Forward search frontier
|
||||
revFrontier := revPath.point // Reverse search frontier
|
||||
|
||||
// Search budget bounds the cost of searching for better paths.
|
||||
// The longest sequence of non-matching symbols that can be tolerated is
|
||||
// approximately the square-root of the search budget.
|
||||
searchBudget := 4 * (nx + ny) // O(n)
|
||||
|
||||
// The algorithm below is a greedy, meet-in-the-middle algorithm for
|
||||
// computing sub-optimal edit-scripts between two lists.
|
||||
//
|
||||
// The algorithm is approximately as follows:
|
||||
// • Searching for differences switches back-and-forth between
|
||||
// a search that starts at the beginning (the top-left corner), and
|
||||
// a search that starts at the end (the bottom-right corner). The goal of
|
||||
// the search is connect with the search from the opposite corner.
|
||||
// • As we search, we build a path in a greedy manner, where the first
|
||||
// match seen is added to the path (this is sub-optimal, but provides a
|
||||
// decent result in practice). When matches are found, we try the next pair
|
||||
// of symbols in the lists and follow all matches as far as possible.
|
||||
// • When searching for matches, we search along a diagonal going through
|
||||
// through the "frontier" point. If no matches are found, we advance the
|
||||
// frontier towards the opposite corner.
|
||||
// • This algorithm terminates when either the X coordinates or the
|
||||
// Y coordinates of the forward and reverse frontier points ever intersect.
|
||||
//
|
||||
// This algorithm is correct even if searching only in the forward direction
|
||||
// or in the reverse direction. We do both because it is commonly observed
|
||||
// that two lists commonly differ because elements were added to the front
|
||||
// or end of the other list.
|
||||
//
|
||||
// Running the tests with the "debug" build tag prints a visualization of
|
||||
// the algorithm running in real-time. This is educational for understanding
|
||||
// how the algorithm works. See debug_enable.go.
|
||||
f = debug.Begin(nx, ny, f, &fwdPath.es, &revPath.es)
|
||||
for {
|
||||
// Forward search from the beginning.
|
||||
if fwdFrontier.X >= revFrontier.X || fwdFrontier.Y >= revFrontier.Y || searchBudget == 0 {
|
||||
break
|
||||
}
|
||||
for stop1, stop2, i := false, false, 0; !(stop1 && stop2) && searchBudget > 0; i++ {
|
||||
// Search in a diagonal pattern for a match.
|
||||
z := zigzag(i)
|
||||
p := point{fwdFrontier.X + z, fwdFrontier.Y - z}
|
||||
switch {
|
||||
case p.X >= revPath.X || p.Y < fwdPath.Y:
|
||||
stop1 = true // Hit top-right corner
|
||||
case p.Y >= revPath.Y || p.X < fwdPath.X:
|
||||
stop2 = true // Hit bottom-left corner
|
||||
case f(p.X, p.Y).Equal():
|
||||
// Match found, so connect the path to this point.
|
||||
fwdPath.connect(p, f)
|
||||
fwdPath.append(Identity)
|
||||
// Follow sequence of matches as far as possible.
|
||||
for fwdPath.X < revPath.X && fwdPath.Y < revPath.Y {
|
||||
if !f(fwdPath.X, fwdPath.Y).Equal() {
|
||||
break
|
||||
}
|
||||
fwdPath.append(Identity)
|
||||
}
|
||||
fwdFrontier = fwdPath.point
|
||||
stop1, stop2 = true, true
|
||||
default:
|
||||
searchBudget-- // Match not found
|
||||
}
|
||||
debug.Update()
|
||||
}
|
||||
// Advance the frontier towards reverse point.
|
||||
if revPath.X-fwdFrontier.X >= revPath.Y-fwdFrontier.Y {
|
||||
fwdFrontier.X++
|
||||
} else {
|
||||
fwdFrontier.Y++
|
||||
}
|
||||
|
||||
// Reverse search from the end.
|
||||
if fwdFrontier.X >= revFrontier.X || fwdFrontier.Y >= revFrontier.Y || searchBudget == 0 {
|
||||
break
|
||||
}
|
||||
for stop1, stop2, i := false, false, 0; !(stop1 && stop2) && searchBudget > 0; i++ {
|
||||
// Search in a diagonal pattern for a match.
|
||||
z := zigzag(i)
|
||||
p := point{revFrontier.X - z, revFrontier.Y + z}
|
||||
switch {
|
||||
case fwdPath.X >= p.X || revPath.Y < p.Y:
|
||||
stop1 = true // Hit bottom-left corner
|
||||
case fwdPath.Y >= p.Y || revPath.X < p.X:
|
||||
stop2 = true // Hit top-right corner
|
||||
case f(p.X-1, p.Y-1).Equal():
|
||||
// Match found, so connect the path to this point.
|
||||
revPath.connect(p, f)
|
||||
revPath.append(Identity)
|
||||
// Follow sequence of matches as far as possible.
|
||||
for fwdPath.X < revPath.X && fwdPath.Y < revPath.Y {
|
||||
if !f(revPath.X-1, revPath.Y-1).Equal() {
|
||||
break
|
||||
}
|
||||
revPath.append(Identity)
|
||||
}
|
||||
revFrontier = revPath.point
|
||||
stop1, stop2 = true, true
|
||||
default:
|
||||
searchBudget-- // Match not found
|
||||
}
|
||||
debug.Update()
|
||||
}
|
||||
// Advance the frontier towards forward point.
|
||||
if revFrontier.X-fwdPath.X >= revFrontier.Y-fwdPath.Y {
|
||||
revFrontier.X--
|
||||
} else {
|
||||
revFrontier.Y--
|
||||
}
|
||||
}
|
||||
|
||||
// Join the forward and reverse paths and then append the reverse path.
|
||||
fwdPath.connect(revPath.point, f)
|
||||
for i := len(revPath.es) - 1; i >= 0; i-- {
|
||||
t := revPath.es[i]
|
||||
revPath.es = revPath.es[:i]
|
||||
fwdPath.append(t)
|
||||
}
|
||||
debug.Finish()
|
||||
return fwdPath.es
|
||||
}
|
||||
|
||||
type path struct {
|
||||
dir int // +1 if forward, -1 if reverse
|
||||
point // Leading point of the EditScript path
|
||||
es EditScript
|
||||
}
|
||||
|
||||
// connect appends any necessary Identity, Modified, UniqueX, or UniqueY types
|
||||
// to the edit-script to connect p.point to dst.
|
||||
func (p *path) connect(dst point, f EqualFunc) {
|
||||
if p.dir > 0 {
|
||||
// Connect in forward direction.
|
||||
for dst.X > p.X && dst.Y > p.Y {
|
||||
switch r := f(p.X, p.Y); {
|
||||
case r.Equal():
|
||||
p.append(Identity)
|
||||
case r.Similar():
|
||||
p.append(Modified)
|
||||
case dst.X-p.X >= dst.Y-p.Y:
|
||||
p.append(UniqueX)
|
||||
default:
|
||||
p.append(UniqueY)
|
||||
}
|
||||
}
|
||||
for dst.X > p.X {
|
||||
p.append(UniqueX)
|
||||
}
|
||||
for dst.Y > p.Y {
|
||||
p.append(UniqueY)
|
||||
}
|
||||
} else {
|
||||
// Connect in reverse direction.
|
||||
for p.X > dst.X && p.Y > dst.Y {
|
||||
switch r := f(p.X-1, p.Y-1); {
|
||||
case r.Equal():
|
||||
p.append(Identity)
|
||||
case r.Similar():
|
||||
p.append(Modified)
|
||||
case p.Y-dst.Y >= p.X-dst.X:
|
||||
p.append(UniqueY)
|
||||
default:
|
||||
p.append(UniqueX)
|
||||
}
|
||||
}
|
||||
for p.X > dst.X {
|
||||
p.append(UniqueX)
|
||||
}
|
||||
for p.Y > dst.Y {
|
||||
p.append(UniqueY)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func (p *path) append(t EditType) {
|
||||
p.es = append(p.es, t)
|
||||
switch t {
|
||||
case Identity, Modified:
|
||||
p.add(p.dir, p.dir)
|
||||
case UniqueX:
|
||||
p.add(p.dir, 0)
|
||||
case UniqueY:
|
||||
p.add(0, p.dir)
|
||||
}
|
||||
debug.Update()
|
||||
}
|
||||
|
||||
type point struct{ X, Y int }
|
||||
|
||||
func (p *point) add(dx, dy int) { p.X += dx; p.Y += dy }
|
||||
|
||||
// zigzag maps a consecutive sequence of integers to a zig-zag sequence.
|
||||
// [0 1 2 3 4 5 ...] => [0 -1 +1 -2 +2 ...]
|
||||
func zigzag(x int) int {
|
||||
if x&1 != 0 {
|
||||
x = ^x
|
||||
}
|
||||
return x >> 1
|
||||
}
|
49
vendor/github.com/google/go-cmp/cmp/internal/function/func.go
generated
vendored
Normal file
49
vendor/github.com/google/go-cmp/cmp/internal/function/func.go
generated
vendored
Normal file
|
@ -0,0 +1,49 @@
|
|||
// Copyright 2017, The Go Authors. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE.md file.
|
||||
|
||||
// Package function identifies function types.
|
||||
package function
|
||||
|
||||
import "reflect"
|
||||
|
||||
type funcType int
|
||||
|
||||
const (
|
||||
_ funcType = iota
|
||||
|
||||
ttbFunc // func(T, T) bool
|
||||
tibFunc // func(T, I) bool
|
||||
trFunc // func(T) R
|
||||
|
||||
Equal = ttbFunc // func(T, T) bool
|
||||
EqualAssignable = tibFunc // func(T, I) bool; encapsulates func(T, T) bool
|
||||
Transformer = trFunc // func(T) R
|
||||
ValueFilter = ttbFunc // func(T, T) bool
|
||||
Less = ttbFunc // func(T, T) bool
|
||||
)
|
||||
|
||||
var boolType = reflect.TypeOf(true)
|
||||
|
||||
// IsType reports whether the reflect.Type is of the specified function type.
|
||||
func IsType(t reflect.Type, ft funcType) bool {
|
||||
if t == nil || t.Kind() != reflect.Func || t.IsVariadic() {
|
||||
return false
|
||||
}
|
||||
ni, no := t.NumIn(), t.NumOut()
|
||||
switch ft {
|
||||
case ttbFunc: // func(T, T) bool
|
||||
if ni == 2 && no == 1 && t.In(0) == t.In(1) && t.Out(0) == boolType {
|
||||
return true
|
||||
}
|
||||
case tibFunc: // func(T, I) bool
|
||||
if ni == 2 && no == 1 && t.In(0).AssignableTo(t.In(1)) && t.Out(0) == boolType {
|
||||
return true
|
||||
}
|
||||
case trFunc: // func(T) R
|
||||
if ni == 1 && no == 1 {
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
277
vendor/github.com/google/go-cmp/cmp/internal/value/format.go
generated
vendored
Normal file
277
vendor/github.com/google/go-cmp/cmp/internal/value/format.go
generated
vendored
Normal file
|
@ -0,0 +1,277 @@
|
|||
// Copyright 2017, The Go Authors. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE.md file.
|
||||
|
||||
// Package value provides functionality for reflect.Value types.
|
||||
package value
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"reflect"
|
||||
"strconv"
|
||||
"strings"
|
||||
"unicode"
|
||||
)
|
||||
|
||||
var stringerIface = reflect.TypeOf((*fmt.Stringer)(nil)).Elem()
|
||||
|
||||
// Format formats the value v as a string.
|
||||
//
|
||||
// This is similar to fmt.Sprintf("%+v", v) except this:
|
||||
// * Prints the type unless it can be elided
|
||||
// * Avoids printing struct fields that are zero
|
||||
// * Prints a nil-slice as being nil, not empty
|
||||
// * Prints map entries in deterministic order
|
||||
func Format(v reflect.Value, conf FormatConfig) string {
|
||||
conf.printType = true
|
||||
conf.followPointers = true
|
||||
conf.realPointers = true
|
||||
return formatAny(v, conf, nil)
|
||||
}
|
||||
|
||||
type FormatConfig struct {
|
||||
UseStringer bool // Should the String method be used if available?
|
||||
printType bool // Should we print the type before the value?
|
||||
PrintPrimitiveType bool // Should we print the type of primitives?
|
||||
followPointers bool // Should we recursively follow pointers?
|
||||
realPointers bool // Should we print the real address of pointers?
|
||||
}
|
||||
|
||||
func formatAny(v reflect.Value, conf FormatConfig, visited map[uintptr]bool) string {
|
||||
// TODO: Should this be a multi-line printout in certain situations?
|
||||
|
||||
if !v.IsValid() {
|
||||
return "<non-existent>"
|
||||
}
|
||||
if conf.UseStringer && v.Type().Implements(stringerIface) && v.CanInterface() {
|
||||
if (v.Kind() == reflect.Ptr || v.Kind() == reflect.Interface) && v.IsNil() {
|
||||
return "<nil>"
|
||||
}
|
||||
|
||||
const stringerPrefix = "s" // Indicates that the String method was used
|
||||
s := v.Interface().(fmt.Stringer).String()
|
||||
return stringerPrefix + formatString(s)
|
||||
}
|
||||
|
||||
switch v.Kind() {
|
||||
case reflect.Bool:
|
||||
return formatPrimitive(v.Type(), v.Bool(), conf)
|
||||
case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64:
|
||||
return formatPrimitive(v.Type(), v.Int(), conf)
|
||||
case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64, reflect.Uintptr:
|
||||
if v.Type().PkgPath() == "" || v.Kind() == reflect.Uintptr {
|
||||
// Unnamed uints are usually bytes or words, so use hexadecimal.
|
||||
return formatPrimitive(v.Type(), formatHex(v.Uint()), conf)
|
||||
}
|
||||
return formatPrimitive(v.Type(), v.Uint(), conf)
|
||||
case reflect.Float32, reflect.Float64:
|
||||
return formatPrimitive(v.Type(), v.Float(), conf)
|
||||
case reflect.Complex64, reflect.Complex128:
|
||||
return formatPrimitive(v.Type(), v.Complex(), conf)
|
||||
case reflect.String:
|
||||
return formatPrimitive(v.Type(), formatString(v.String()), conf)
|
||||
case reflect.UnsafePointer, reflect.Chan, reflect.Func:
|
||||
return formatPointer(v, conf)
|
||||
case reflect.Ptr:
|
||||
if v.IsNil() {
|
||||
if conf.printType {
|
||||
return fmt.Sprintf("(%v)(nil)", v.Type())
|
||||
}
|
||||
return "<nil>"
|
||||
}
|
||||
if visited[v.Pointer()] || !conf.followPointers {
|
||||
return formatPointer(v, conf)
|
||||
}
|
||||
visited = insertPointer(visited, v.Pointer())
|
||||
return "&" + formatAny(v.Elem(), conf, visited)
|
||||
case reflect.Interface:
|
||||
if v.IsNil() {
|
||||
if conf.printType {
|
||||
return fmt.Sprintf("%v(nil)", v.Type())
|
||||
}
|
||||
return "<nil>"
|
||||
}
|
||||
return formatAny(v.Elem(), conf, visited)
|
||||
case reflect.Slice:
|
||||
if v.IsNil() {
|
||||
if conf.printType {
|
||||
return fmt.Sprintf("%v(nil)", v.Type())
|
||||
}
|
||||
return "<nil>"
|
||||
}
|
||||
if visited[v.Pointer()] {
|
||||
return formatPointer(v, conf)
|
||||
}
|
||||
visited = insertPointer(visited, v.Pointer())
|
||||
fallthrough
|
||||
case reflect.Array:
|
||||
var ss []string
|
||||
subConf := conf
|
||||
subConf.printType = v.Type().Elem().Kind() == reflect.Interface
|
||||
for i := 0; i < v.Len(); i++ {
|
||||
s := formatAny(v.Index(i), subConf, visited)
|
||||
ss = append(ss, s)
|
||||
}
|
||||
s := fmt.Sprintf("{%s}", strings.Join(ss, ", "))
|
||||
if conf.printType {
|
||||
return v.Type().String() + s
|
||||
}
|
||||
return s
|
||||
case reflect.Map:
|
||||
if v.IsNil() {
|
||||
if conf.printType {
|
||||
return fmt.Sprintf("%v(nil)", v.Type())
|
||||
}
|
||||
return "<nil>"
|
||||
}
|
||||
if visited[v.Pointer()] {
|
||||
return formatPointer(v, conf)
|
||||
}
|
||||
visited = insertPointer(visited, v.Pointer())
|
||||
|
||||
var ss []string
|
||||
keyConf, valConf := conf, conf
|
||||
keyConf.printType = v.Type().Key().Kind() == reflect.Interface
|
||||
keyConf.followPointers = false
|
||||
valConf.printType = v.Type().Elem().Kind() == reflect.Interface
|
||||
for _, k := range SortKeys(v.MapKeys()) {
|
||||
sk := formatAny(k, keyConf, visited)
|
||||
sv := formatAny(v.MapIndex(k), valConf, visited)
|
||||
ss = append(ss, fmt.Sprintf("%s: %s", sk, sv))
|
||||
}
|
||||
s := fmt.Sprintf("{%s}", strings.Join(ss, ", "))
|
||||
if conf.printType {
|
||||
return v.Type().String() + s
|
||||
}
|
||||
return s
|
||||
case reflect.Struct:
|
||||
var ss []string
|
||||
subConf := conf
|
||||
subConf.printType = true
|
||||
for i := 0; i < v.NumField(); i++ {
|
||||
vv := v.Field(i)
|
||||
if isZero(vv) {
|
||||
continue // Elide zero value fields
|
||||
}
|
||||
name := v.Type().Field(i).Name
|
||||
subConf.UseStringer = conf.UseStringer
|
||||
s := formatAny(vv, subConf, visited)
|
||||
ss = append(ss, fmt.Sprintf("%s: %s", name, s))
|
||||
}
|
||||
s := fmt.Sprintf("{%s}", strings.Join(ss, ", "))
|
||||
if conf.printType {
|
||||
return v.Type().String() + s
|
||||
}
|
||||
return s
|
||||
default:
|
||||
panic(fmt.Sprintf("%v kind not handled", v.Kind()))
|
||||
}
|
||||
}
|
||||
|
||||
func formatString(s string) string {
|
||||
// Use quoted string if it the same length as a raw string literal.
|
||||
// Otherwise, attempt to use the raw string form.
|
||||
qs := strconv.Quote(s)
|
||||
if len(qs) == 1+len(s)+1 {
|
||||
return qs
|
||||
}
|
||||
|
||||
// Disallow newlines to ensure output is a single line.
|
||||
// Only allow printable runes for readability purposes.
|
||||
rawInvalid := func(r rune) bool {
|
||||
return r == '`' || r == '\n' || !unicode.IsPrint(r)
|
||||
}
|
||||
if strings.IndexFunc(s, rawInvalid) < 0 {
|
||||
return "`" + s + "`"
|
||||
}
|
||||
return qs
|
||||
}
|
||||
|
||||
func formatPrimitive(t reflect.Type, v interface{}, conf FormatConfig) string {
|
||||
if conf.printType && (conf.PrintPrimitiveType || t.PkgPath() != "") {
|
||||
return fmt.Sprintf("%v(%v)", t, v)
|
||||
}
|
||||
return fmt.Sprintf("%v", v)
|
||||
}
|
||||
|
||||
func formatPointer(v reflect.Value, conf FormatConfig) string {
|
||||
p := v.Pointer()
|
||||
if !conf.realPointers {
|
||||
p = 0 // For deterministic printing purposes
|
||||
}
|
||||
s := formatHex(uint64(p))
|
||||
if conf.printType {
|
||||
return fmt.Sprintf("(%v)(%s)", v.Type(), s)
|
||||
}
|
||||
return s
|
||||
}
|
||||
|
||||
func formatHex(u uint64) string {
|
||||
var f string
|
||||
switch {
|
||||
case u <= 0xff:
|
||||
f = "0x%02x"
|
||||
case u <= 0xffff:
|
||||
f = "0x%04x"
|
||||
case u <= 0xffffff:
|
||||
f = "0x%06x"
|
||||
case u <= 0xffffffff:
|
||||
f = "0x%08x"
|
||||
case u <= 0xffffffffff:
|
||||
f = "0x%010x"
|
||||
case u <= 0xffffffffffff:
|
||||
f = "0x%012x"
|
||||
case u <= 0xffffffffffffff:
|
||||
f = "0x%014x"
|
||||
case u <= 0xffffffffffffffff:
|
||||
f = "0x%016x"
|
||||
}
|
||||
return fmt.Sprintf(f, u)
|
||||
}
|
||||
|
||||
// insertPointer insert p into m, allocating m if necessary.
|
||||
func insertPointer(m map[uintptr]bool, p uintptr) map[uintptr]bool {
|
||||
if m == nil {
|
||||
m = make(map[uintptr]bool)
|
||||
}
|
||||
m[p] = true
|
||||
return m
|
||||
}
|
||||
|
||||
// isZero reports whether v is the zero value.
|
||||
// This does not rely on Interface and so can be used on unexported fields.
|
||||
func isZero(v reflect.Value) bool {
|
||||
switch v.Kind() {
|
||||
case reflect.Bool:
|
||||
return v.Bool() == false
|
||||
case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64:
|
||||
return v.Int() == 0
|
||||
case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64, reflect.Uintptr:
|
||||
return v.Uint() == 0
|
||||
case reflect.Float32, reflect.Float64:
|
||||
return v.Float() == 0
|
||||
case reflect.Complex64, reflect.Complex128:
|
||||
return v.Complex() == 0
|
||||
case reflect.String:
|
||||
return v.String() == ""
|
||||
case reflect.UnsafePointer:
|
||||
return v.Pointer() == 0
|
||||
case reflect.Chan, reflect.Func, reflect.Interface, reflect.Ptr, reflect.Map, reflect.Slice:
|
||||
return v.IsNil()
|
||||
case reflect.Array:
|
||||
for i := 0; i < v.Len(); i++ {
|
||||
if !isZero(v.Index(i)) {
|
||||
return false
|
||||
}
|
||||
}
|
||||
return true
|
||||
case reflect.Struct:
|
||||
for i := 0; i < v.NumField(); i++ {
|
||||
if !isZero(v.Field(i)) {
|
||||
return false
|
||||
}
|
||||
}
|
||||
return true
|
||||
}
|
||||
return false
|
||||
}
|
111
vendor/github.com/google/go-cmp/cmp/internal/value/sort.go
generated
vendored
Normal file
111
vendor/github.com/google/go-cmp/cmp/internal/value/sort.go
generated
vendored
Normal file
|
@ -0,0 +1,111 @@
|
|||
// Copyright 2017, The Go Authors. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE.md file.
|
||||
|
||||
package value
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"math"
|
||||
"reflect"
|
||||
"sort"
|
||||
)
|
||||
|
||||
// SortKeys sorts a list of map keys, deduplicating keys if necessary.
|
||||
// The type of each value must be comparable.
|
||||
func SortKeys(vs []reflect.Value) []reflect.Value {
|
||||
if len(vs) == 0 {
|
||||
return vs
|
||||
}
|
||||
|
||||
// Sort the map keys.
|
||||
sort.Sort(valueSorter(vs))
|
||||
|
||||
// Deduplicate keys (fails for NaNs).
|
||||
vs2 := vs[:1]
|
||||
for _, v := range vs[1:] {
|
||||
if isLess(vs2[len(vs2)-1], v) {
|
||||
vs2 = append(vs2, v)
|
||||
}
|
||||
}
|
||||
return vs2
|
||||
}
|
||||
|
||||
// TODO: Use sort.Slice once Google AppEngine is on Go1.8 or above.
|
||||
type valueSorter []reflect.Value
|
||||
|
||||
func (vs valueSorter) Len() int { return len(vs) }
|
||||
func (vs valueSorter) Less(i, j int) bool { return isLess(vs[i], vs[j]) }
|
||||
func (vs valueSorter) Swap(i, j int) { vs[i], vs[j] = vs[j], vs[i] }
|
||||
|
||||
// isLess is a generic function for sorting arbitrary map keys.
|
||||
// The inputs must be of the same type and must be comparable.
|
||||
func isLess(x, y reflect.Value) bool {
|
||||
switch x.Type().Kind() {
|
||||
case reflect.Bool:
|
||||
return !x.Bool() && y.Bool()
|
||||
case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64:
|
||||
return x.Int() < y.Int()
|
||||
case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64, reflect.Uintptr:
|
||||
return x.Uint() < y.Uint()
|
||||
case reflect.Float32, reflect.Float64:
|
||||
fx, fy := x.Float(), y.Float()
|
||||
return fx < fy || math.IsNaN(fx) && !math.IsNaN(fy)
|
||||
case reflect.Complex64, reflect.Complex128:
|
||||
cx, cy := x.Complex(), y.Complex()
|
||||
rx, ix, ry, iy := real(cx), imag(cx), real(cy), imag(cy)
|
||||
if rx == ry || (math.IsNaN(rx) && math.IsNaN(ry)) {
|
||||
return ix < iy || math.IsNaN(ix) && !math.IsNaN(iy)
|
||||
}
|
||||
return rx < ry || math.IsNaN(rx) && !math.IsNaN(ry)
|
||||
case reflect.Ptr, reflect.UnsafePointer, reflect.Chan:
|
||||
return x.Pointer() < y.Pointer()
|
||||
case reflect.String:
|
||||
return x.String() < y.String()
|
||||
case reflect.Array:
|
||||
for i := 0; i < x.Len(); i++ {
|
||||
if isLess(x.Index(i), y.Index(i)) {
|
||||
return true
|
||||
}
|
||||
if isLess(y.Index(i), x.Index(i)) {
|
||||
return false
|
||||
}
|
||||
}
|
||||
return false
|
||||
case reflect.Struct:
|
||||
for i := 0; i < x.NumField(); i++ {
|
||||
if isLess(x.Field(i), y.Field(i)) {
|
||||
return true
|
||||
}
|
||||
if isLess(y.Field(i), x.Field(i)) {
|
||||
return false
|
||||
}
|
||||
}
|
||||
return false
|
||||
case reflect.Interface:
|
||||
vx, vy := x.Elem(), y.Elem()
|
||||
if !vx.IsValid() || !vy.IsValid() {
|
||||
return !vx.IsValid() && vy.IsValid()
|
||||
}
|
||||
tx, ty := vx.Type(), vy.Type()
|
||||
if tx == ty {
|
||||
return isLess(x.Elem(), y.Elem())
|
||||
}
|
||||
if tx.Kind() != ty.Kind() {
|
||||
return vx.Kind() < vy.Kind()
|
||||
}
|
||||
if tx.String() != ty.String() {
|
||||
return tx.String() < ty.String()
|
||||
}
|
||||
if tx.PkgPath() != ty.PkgPath() {
|
||||
return tx.PkgPath() < ty.PkgPath()
|
||||
}
|
||||
// This can happen in rare situations, so we fallback to just comparing
|
||||
// the unique pointer for a reflect.Type. This guarantees deterministic
|
||||
// ordering within a program, but it is obviously not stable.
|
||||
return reflect.ValueOf(vx.Type()).Pointer() < reflect.ValueOf(vy.Type()).Pointer()
|
||||
default:
|
||||
// Must be Func, Map, or Slice; which are not comparable.
|
||||
panic(fmt.Sprintf("%T is not comparable", x.Type()))
|
||||
}
|
||||
}
|
453
vendor/github.com/google/go-cmp/cmp/options.go
generated
vendored
Normal file
453
vendor/github.com/google/go-cmp/cmp/options.go
generated
vendored
Normal file
|
@ -0,0 +1,453 @@
|
|||
// Copyright 2017, The Go Authors. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE.md file.
|
||||
|
||||
package cmp
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"reflect"
|
||||
"runtime"
|
||||
"strings"
|
||||
|
||||
"github.com/google/go-cmp/cmp/internal/function"
|
||||
)
|
||||
|
||||
// Option configures for specific behavior of Equal and Diff. In particular,
|
||||
// the fundamental Option functions (Ignore, Transformer, and Comparer),
|
||||
// configure how equality is determined.
|
||||
//
|
||||
// The fundamental options may be composed with filters (FilterPath and
|
||||
// FilterValues) to control the scope over which they are applied.
|
||||
//
|
||||
// The cmp/cmpopts package provides helper functions for creating options that
|
||||
// may be used with Equal and Diff.
|
||||
type Option interface {
|
||||
// filter applies all filters and returns the option that remains.
|
||||
// Each option may only read s.curPath and call s.callTTBFunc.
|
||||
//
|
||||
// An Options is returned only if multiple comparers or transformers
|
||||
// can apply simultaneously and will only contain values of those types
|
||||
// or sub-Options containing values of those types.
|
||||
filter(s *state, vx, vy reflect.Value, t reflect.Type) applicableOption
|
||||
}
|
||||
|
||||
// applicableOption represents the following types:
|
||||
// Fundamental: ignore | invalid | *comparer | *transformer
|
||||
// Grouping: Options
|
||||
type applicableOption interface {
|
||||
Option
|
||||
|
||||
// apply executes the option, which may mutate s or panic.
|
||||
apply(s *state, vx, vy reflect.Value)
|
||||
}
|
||||
|
||||
// coreOption represents the following types:
|
||||
// Fundamental: ignore | invalid | *comparer | *transformer
|
||||
// Filters: *pathFilter | *valuesFilter
|
||||
type coreOption interface {
|
||||
Option
|
||||
isCore()
|
||||
}
|
||||
|
||||
type core struct{}
|
||||
|
||||
func (core) isCore() {}
|
||||
|
||||
// Options is a list of Option values that also satisfies the Option interface.
|
||||
// Helper comparison packages may return an Options value when packing multiple
|
||||
// Option values into a single Option. When this package processes an Options,
|
||||
// it will be implicitly expanded into a flat list.
|
||||
//
|
||||
// Applying a filter on an Options is equivalent to applying that same filter
|
||||
// on all individual options held within.
|
||||
type Options []Option
|
||||
|
||||
func (opts Options) filter(s *state, vx, vy reflect.Value, t reflect.Type) (out applicableOption) {
|
||||
for _, opt := range opts {
|
||||
switch opt := opt.filter(s, vx, vy, t); opt.(type) {
|
||||
case ignore:
|
||||
return ignore{} // Only ignore can short-circuit evaluation
|
||||
case invalid:
|
||||
out = invalid{} // Takes precedence over comparer or transformer
|
||||
case *comparer, *transformer, Options:
|
||||
switch out.(type) {
|
||||
case nil:
|
||||
out = opt
|
||||
case invalid:
|
||||
// Keep invalid
|
||||
case *comparer, *transformer, Options:
|
||||
out = Options{out, opt} // Conflicting comparers or transformers
|
||||
}
|
||||
}
|
||||
}
|
||||
return out
|
||||
}
|
||||
|
||||
func (opts Options) apply(s *state, _, _ reflect.Value) {
|
||||
const warning = "ambiguous set of applicable options"
|
||||
const help = "consider using filters to ensure at most one Comparer or Transformer may apply"
|
||||
var ss []string
|
||||
for _, opt := range flattenOptions(nil, opts) {
|
||||
ss = append(ss, fmt.Sprint(opt))
|
||||
}
|
||||
set := strings.Join(ss, "\n\t")
|
||||
panic(fmt.Sprintf("%s at %#v:\n\t%s\n%s", warning, s.curPath, set, help))
|
||||
}
|
||||
|
||||
func (opts Options) String() string {
|
||||
var ss []string
|
||||
for _, opt := range opts {
|
||||
ss = append(ss, fmt.Sprint(opt))
|
||||
}
|
||||
return fmt.Sprintf("Options{%s}", strings.Join(ss, ", "))
|
||||
}
|
||||
|
||||
// FilterPath returns a new Option where opt is only evaluated if filter f
|
||||
// returns true for the current Path in the value tree.
|
||||
//
|
||||
// The option passed in may be an Ignore, Transformer, Comparer, Options, or
|
||||
// a previously filtered Option.
|
||||
func FilterPath(f func(Path) bool, opt Option) Option {
|
||||
if f == nil {
|
||||
panic("invalid path filter function")
|
||||
}
|
||||
if opt := normalizeOption(opt); opt != nil {
|
||||
return &pathFilter{fnc: f, opt: opt}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
type pathFilter struct {
|
||||
core
|
||||
fnc func(Path) bool
|
||||
opt Option
|
||||
}
|
||||
|
||||
func (f pathFilter) filter(s *state, vx, vy reflect.Value, t reflect.Type) applicableOption {
|
||||
if f.fnc(s.curPath) {
|
||||
return f.opt.filter(s, vx, vy, t)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (f pathFilter) String() string {
|
||||
fn := getFuncName(reflect.ValueOf(f.fnc).Pointer())
|
||||
return fmt.Sprintf("FilterPath(%s, %v)", fn, f.opt)
|
||||
}
|
||||
|
||||
// FilterValues returns a new Option where opt is only evaluated if filter f,
|
||||
// which is a function of the form "func(T, T) bool", returns true for the
|
||||
// current pair of values being compared. If the type of the values is not
|
||||
// assignable to T, then this filter implicitly returns false.
|
||||
//
|
||||
// The filter function must be
|
||||
// symmetric (i.e., agnostic to the order of the inputs) and
|
||||
// deterministic (i.e., produces the same result when given the same inputs).
|
||||
// If T is an interface, it is possible that f is called with two values with
|
||||
// different concrete types that both implement T.
|
||||
//
|
||||
// The option passed in may be an Ignore, Transformer, Comparer, Options, or
|
||||
// a previously filtered Option.
|
||||
func FilterValues(f interface{}, opt Option) Option {
|
||||
v := reflect.ValueOf(f)
|
||||
if !function.IsType(v.Type(), function.ValueFilter) || v.IsNil() {
|
||||
panic(fmt.Sprintf("invalid values filter function: %T", f))
|
||||
}
|
||||
if opt := normalizeOption(opt); opt != nil {
|
||||
vf := &valuesFilter{fnc: v, opt: opt}
|
||||
if ti := v.Type().In(0); ti.Kind() != reflect.Interface || ti.NumMethod() > 0 {
|
||||
vf.typ = ti
|
||||
}
|
||||
return vf
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
type valuesFilter struct {
|
||||
core
|
||||
typ reflect.Type // T
|
||||
fnc reflect.Value // func(T, T) bool
|
||||
opt Option
|
||||
}
|
||||
|
||||
func (f valuesFilter) filter(s *state, vx, vy reflect.Value, t reflect.Type) applicableOption {
|
||||
if !vx.IsValid() || !vy.IsValid() {
|
||||
return invalid{}
|
||||
}
|
||||
if (f.typ == nil || t.AssignableTo(f.typ)) && s.callTTBFunc(f.fnc, vx, vy) {
|
||||
return f.opt.filter(s, vx, vy, t)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (f valuesFilter) String() string {
|
||||
fn := getFuncName(f.fnc.Pointer())
|
||||
return fmt.Sprintf("FilterValues(%s, %v)", fn, f.opt)
|
||||
}
|
||||
|
||||
// Ignore is an Option that causes all comparisons to be ignored.
|
||||
// This value is intended to be combined with FilterPath or FilterValues.
|
||||
// It is an error to pass an unfiltered Ignore option to Equal.
|
||||
func Ignore() Option { return ignore{} }
|
||||
|
||||
type ignore struct{ core }
|
||||
|
||||
func (ignore) isFiltered() bool { return false }
|
||||
func (ignore) filter(_ *state, _, _ reflect.Value, _ reflect.Type) applicableOption { return ignore{} }
|
||||
func (ignore) apply(_ *state, _, _ reflect.Value) { return }
|
||||
func (ignore) String() string { return "Ignore()" }
|
||||
|
||||
// invalid is a sentinel Option type to indicate that some options could not
|
||||
// be evaluated due to unexported fields.
|
||||
type invalid struct{ core }
|
||||
|
||||
func (invalid) filter(_ *state, _, _ reflect.Value, _ reflect.Type) applicableOption { return invalid{} }
|
||||
func (invalid) apply(s *state, _, _ reflect.Value) {
|
||||
const help = "consider using AllowUnexported or cmpopts.IgnoreUnexported"
|
||||
panic(fmt.Sprintf("cannot handle unexported field: %#v\n%s", s.curPath, help))
|
||||
}
|
||||
|
||||
// Transformer returns an Option that applies a transformation function that
|
||||
// converts values of a certain type into that of another.
|
||||
//
|
||||
// The transformer f must be a function "func(T) R" that converts values of
|
||||
// type T to those of type R and is implicitly filtered to input values
|
||||
// assignable to T. The transformer must not mutate T in any way.
|
||||
//
|
||||
// To help prevent some cases of infinite recursive cycles applying the
|
||||
// same transform to the output of itself (e.g., in the case where the
|
||||
// input and output types are the same), an implicit filter is added such that
|
||||
// a transformer is applicable only if that exact transformer is not already
|
||||
// in the tail of the Path since the last non-Transform step.
|
||||
//
|
||||
// The name is a user provided label that is used as the Transform.Name in the
|
||||
// transformation PathStep. If empty, an arbitrary name is used.
|
||||
func Transformer(name string, f interface{}) Option {
|
||||
v := reflect.ValueOf(f)
|
||||
if !function.IsType(v.Type(), function.Transformer) || v.IsNil() {
|
||||
panic(fmt.Sprintf("invalid transformer function: %T", f))
|
||||
}
|
||||
if name == "" {
|
||||
name = "λ" // Lambda-symbol as place-holder for anonymous transformer
|
||||
}
|
||||
if !isValid(name) {
|
||||
panic(fmt.Sprintf("invalid name: %q", name))
|
||||
}
|
||||
tr := &transformer{name: name, fnc: reflect.ValueOf(f)}
|
||||
if ti := v.Type().In(0); ti.Kind() != reflect.Interface || ti.NumMethod() > 0 {
|
||||
tr.typ = ti
|
||||
}
|
||||
return tr
|
||||
}
|
||||
|
||||
type transformer struct {
|
||||
core
|
||||
name string
|
||||
typ reflect.Type // T
|
||||
fnc reflect.Value // func(T) R
|
||||
}
|
||||
|
||||
func (tr *transformer) isFiltered() bool { return tr.typ != nil }
|
||||
|
||||
func (tr *transformer) filter(s *state, _, _ reflect.Value, t reflect.Type) applicableOption {
|
||||
for i := len(s.curPath) - 1; i >= 0; i-- {
|
||||
if t, ok := s.curPath[i].(*transform); !ok {
|
||||
break // Hit most recent non-Transform step
|
||||
} else if tr == t.trans {
|
||||
return nil // Cannot directly use same Transform
|
||||
}
|
||||
}
|
||||
if tr.typ == nil || t.AssignableTo(tr.typ) {
|
||||
return tr
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (tr *transformer) apply(s *state, vx, vy reflect.Value) {
|
||||
// Update path before calling the Transformer so that dynamic checks
|
||||
// will use the updated path.
|
||||
s.curPath.push(&transform{pathStep{tr.fnc.Type().Out(0)}, tr})
|
||||
defer s.curPath.pop()
|
||||
|
||||
vx = s.callTRFunc(tr.fnc, vx)
|
||||
vy = s.callTRFunc(tr.fnc, vy)
|
||||
s.compareAny(vx, vy)
|
||||
}
|
||||
|
||||
func (tr transformer) String() string {
|
||||
return fmt.Sprintf("Transformer(%s, %s)", tr.name, getFuncName(tr.fnc.Pointer()))
|
||||
}
|
||||
|
||||
// Comparer returns an Option that determines whether two values are equal
|
||||
// to each other.
|
||||
//
|
||||
// The comparer f must be a function "func(T, T) bool" and is implicitly
|
||||
// filtered to input values assignable to T. If T is an interface, it is
|
||||
// possible that f is called with two values of different concrete types that
|
||||
// both implement T.
|
||||
//
|
||||
// The equality function must be:
|
||||
// • Symmetric: equal(x, y) == equal(y, x)
|
||||
// • Deterministic: equal(x, y) == equal(x, y)
|
||||
// • Pure: equal(x, y) does not modify x or y
|
||||
func Comparer(f interface{}) Option {
|
||||
v := reflect.ValueOf(f)
|
||||
if !function.IsType(v.Type(), function.Equal) || v.IsNil() {
|
||||
panic(fmt.Sprintf("invalid comparer function: %T", f))
|
||||
}
|
||||
cm := &comparer{fnc: v}
|
||||
if ti := v.Type().In(0); ti.Kind() != reflect.Interface || ti.NumMethod() > 0 {
|
||||
cm.typ = ti
|
||||
}
|
||||
return cm
|
||||
}
|
||||
|
||||
type comparer struct {
|
||||
core
|
||||
typ reflect.Type // T
|
||||
fnc reflect.Value // func(T, T) bool
|
||||
}
|
||||
|
||||
func (cm *comparer) isFiltered() bool { return cm.typ != nil }
|
||||
|
||||
func (cm *comparer) filter(_ *state, _, _ reflect.Value, t reflect.Type) applicableOption {
|
||||
if cm.typ == nil || t.AssignableTo(cm.typ) {
|
||||
return cm
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (cm *comparer) apply(s *state, vx, vy reflect.Value) {
|
||||
eq := s.callTTBFunc(cm.fnc, vx, vy)
|
||||
s.report(eq, vx, vy)
|
||||
}
|
||||
|
||||
func (cm comparer) String() string {
|
||||
return fmt.Sprintf("Comparer(%s)", getFuncName(cm.fnc.Pointer()))
|
||||
}
|
||||
|
||||
// AllowUnexported returns an Option that forcibly allows operations on
|
||||
// unexported fields in certain structs, which are specified by passing in a
|
||||
// value of each struct type.
|
||||
//
|
||||
// Users of this option must understand that comparing on unexported fields
|
||||
// from external packages is not safe since changes in the internal
|
||||
// implementation of some external package may cause the result of Equal
|
||||
// to unexpectedly change. However, it may be valid to use this option on types
|
||||
// defined in an internal package where the semantic meaning of an unexported
|
||||
// field is in the control of the user.
|
||||
//
|
||||
// For some cases, a custom Comparer should be used instead that defines
|
||||
// equality as a function of the public API of a type rather than the underlying
|
||||
// unexported implementation.
|
||||
//
|
||||
// For example, the reflect.Type documentation defines equality to be determined
|
||||
// by the == operator on the interface (essentially performing a shallow pointer
|
||||
// comparison) and most attempts to compare *regexp.Regexp types are interested
|
||||
// in only checking that the regular expression strings are equal.
|
||||
// Both of these are accomplished using Comparers:
|
||||
//
|
||||
// Comparer(func(x, y reflect.Type) bool { return x == y })
|
||||
// Comparer(func(x, y *regexp.Regexp) bool { return x.String() == y.String() })
|
||||
//
|
||||
// In other cases, the cmpopts.IgnoreUnexported option can be used to ignore
|
||||
// all unexported fields on specified struct types.
|
||||
func AllowUnexported(types ...interface{}) Option {
|
||||
if !supportAllowUnexported {
|
||||
panic("AllowUnexported is not supported on purego builds, Google App Engine Standard, or GopherJS")
|
||||
}
|
||||
m := make(map[reflect.Type]bool)
|
||||
for _, typ := range types {
|
||||
t := reflect.TypeOf(typ)
|
||||
if t.Kind() != reflect.Struct {
|
||||
panic(fmt.Sprintf("invalid struct type: %T", typ))
|
||||
}
|
||||
m[t] = true
|
||||
}
|
||||
return visibleStructs(m)
|
||||
}
|
||||
|
||||
type visibleStructs map[reflect.Type]bool
|
||||
|
||||
func (visibleStructs) filter(_ *state, _, _ reflect.Value, _ reflect.Type) applicableOption {
|
||||
panic("not implemented")
|
||||
}
|
||||
|
||||
// reporter is an Option that configures how differences are reported.
|
||||
type reporter interface {
|
||||
// TODO: Not exported yet.
|
||||
//
|
||||
// Perhaps add PushStep and PopStep and change Report to only accept
|
||||
// a PathStep instead of the full-path? Adding a PushStep and PopStep makes
|
||||
// it clear that we are traversing the value tree in a depth-first-search
|
||||
// manner, which has an effect on how values are printed.
|
||||
|
||||
Option
|
||||
|
||||
// Report is called for every comparison made and will be provided with
|
||||
// the two values being compared, the equality result, and the
|
||||
// current path in the value tree. It is possible for x or y to be an
|
||||
// invalid reflect.Value if one of the values is non-existent;
|
||||
// which is possible with maps and slices.
|
||||
Report(x, y reflect.Value, eq bool, p Path)
|
||||
}
|
||||
|
||||
// normalizeOption normalizes the input options such that all Options groups
|
||||
// are flattened and groups with a single element are reduced to that element.
|
||||
// Only coreOptions and Options containing coreOptions are allowed.
|
||||
func normalizeOption(src Option) Option {
|
||||
switch opts := flattenOptions(nil, Options{src}); len(opts) {
|
||||
case 0:
|
||||
return nil
|
||||
case 1:
|
||||
return opts[0]
|
||||
default:
|
||||
return opts
|
||||
}
|
||||
}
|
||||
|
||||
// flattenOptions copies all options in src to dst as a flat list.
|
||||
// Only coreOptions and Options containing coreOptions are allowed.
|
||||
func flattenOptions(dst, src Options) Options {
|
||||
for _, opt := range src {
|
||||
switch opt := opt.(type) {
|
||||
case nil:
|
||||
continue
|
||||
case Options:
|
||||
dst = flattenOptions(dst, opt)
|
||||
case coreOption:
|
||||
dst = append(dst, opt)
|
||||
default:
|
||||
panic(fmt.Sprintf("invalid option type: %T", opt))
|
||||
}
|
||||
}
|
||||
return dst
|
||||
}
|
||||
|
||||
// getFuncName returns a short function name from the pointer.
|
||||
// The string parsing logic works up until Go1.9.
|
||||
func getFuncName(p uintptr) string {
|
||||
fnc := runtime.FuncForPC(p)
|
||||
if fnc == nil {
|
||||
return "<unknown>"
|
||||
}
|
||||
name := fnc.Name() // E.g., "long/path/name/mypkg.(mytype).(long/path/name/mypkg.myfunc)-fm"
|
||||
if strings.HasSuffix(name, ")-fm") || strings.HasSuffix(name, ")·fm") {
|
||||
// Strip the package name from method name.
|
||||
name = strings.TrimSuffix(name, ")-fm")
|
||||
name = strings.TrimSuffix(name, ")·fm")
|
||||
if i := strings.LastIndexByte(name, '('); i >= 0 {
|
||||
methodName := name[i+1:] // E.g., "long/path/name/mypkg.myfunc"
|
||||
if j := strings.LastIndexByte(methodName, '.'); j >= 0 {
|
||||
methodName = methodName[j+1:] // E.g., "myfunc"
|
||||
}
|
||||
name = name[:i] + methodName // E.g., "long/path/name/mypkg.(mytype)." + "myfunc"
|
||||
}
|
||||
}
|
||||
if i := strings.LastIndexByte(name, '/'); i >= 0 {
|
||||
// Strip the package name.
|
||||
name = name[i+1:] // E.g., "mypkg.(mytype).myfunc"
|
||||
}
|
||||
return name
|
||||
}
|
309
vendor/github.com/google/go-cmp/cmp/path.go
generated
vendored
Normal file
309
vendor/github.com/google/go-cmp/cmp/path.go
generated
vendored
Normal file
|
@ -0,0 +1,309 @@
|
|||
// Copyright 2017, The Go Authors. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE.md file.
|
||||
|
||||
package cmp
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"reflect"
|
||||
"strings"
|
||||
"unicode"
|
||||
"unicode/utf8"
|
||||
)
|
||||
|
||||
type (
|
||||
// Path is a list of PathSteps describing the sequence of operations to get
|
||||
// from some root type to the current position in the value tree.
|
||||
// The first Path element is always an operation-less PathStep that exists
|
||||
// simply to identify the initial type.
|
||||
//
|
||||
// When traversing structs with embedded structs, the embedded struct will
|
||||
// always be accessed as a field before traversing the fields of the
|
||||
// embedded struct themselves. That is, an exported field from the
|
||||
// embedded struct will never be accessed directly from the parent struct.
|
||||
Path []PathStep
|
||||
|
||||
// PathStep is a union-type for specific operations to traverse
|
||||
// a value's tree structure. Users of this package never need to implement
|
||||
// these types as values of this type will be returned by this package.
|
||||
PathStep interface {
|
||||
String() string
|
||||
Type() reflect.Type // Resulting type after performing the path step
|
||||
isPathStep()
|
||||
}
|
||||
|
||||
// SliceIndex is an index operation on a slice or array at some index Key.
|
||||
SliceIndex interface {
|
||||
PathStep
|
||||
Key() int // May return -1 if in a split state
|
||||
|
||||
// SplitKeys returns the indexes for indexing into slices in the
|
||||
// x and y values, respectively. These indexes may differ due to the
|
||||
// insertion or removal of an element in one of the slices, causing
|
||||
// all of the indexes to be shifted. If an index is -1, then that
|
||||
// indicates that the element does not exist in the associated slice.
|
||||
//
|
||||
// Key is guaranteed to return -1 if and only if the indexes returned
|
||||
// by SplitKeys are not the same. SplitKeys will never return -1 for
|
||||
// both indexes.
|
||||
SplitKeys() (x int, y int)
|
||||
|
||||
isSliceIndex()
|
||||
}
|
||||
// MapIndex is an index operation on a map at some index Key.
|
||||
MapIndex interface {
|
||||
PathStep
|
||||
Key() reflect.Value
|
||||
isMapIndex()
|
||||
}
|
||||
// TypeAssertion represents a type assertion on an interface.
|
||||
TypeAssertion interface {
|
||||
PathStep
|
||||
isTypeAssertion()
|
||||
}
|
||||
// StructField represents a struct field access on a field called Name.
|
||||
StructField interface {
|
||||
PathStep
|
||||
Name() string
|
||||
Index() int
|
||||
isStructField()
|
||||
}
|
||||
// Indirect represents pointer indirection on the parent type.
|
||||
Indirect interface {
|
||||
PathStep
|
||||
isIndirect()
|
||||
}
|
||||
// Transform is a transformation from the parent type to the current type.
|
||||
Transform interface {
|
||||
PathStep
|
||||
Name() string
|
||||
Func() reflect.Value
|
||||
|
||||
// Option returns the originally constructed Transformer option.
|
||||
// The == operator can be used to detect the exact option used.
|
||||
Option() Option
|
||||
|
||||
isTransform()
|
||||
}
|
||||
)
|
||||
|
||||
func (pa *Path) push(s PathStep) {
|
||||
*pa = append(*pa, s)
|
||||
}
|
||||
|
||||
func (pa *Path) pop() {
|
||||
*pa = (*pa)[:len(*pa)-1]
|
||||
}
|
||||
|
||||
// Last returns the last PathStep in the Path.
|
||||
// If the path is empty, this returns a non-nil PathStep that reports a nil Type.
|
||||
func (pa Path) Last() PathStep {
|
||||
return pa.Index(-1)
|
||||
}
|
||||
|
||||
// Index returns the ith step in the Path and supports negative indexing.
|
||||
// A negative index starts counting from the tail of the Path such that -1
|
||||
// refers to the last step, -2 refers to the second-to-last step, and so on.
|
||||
// If index is invalid, this returns a non-nil PathStep that reports a nil Type.
|
||||
func (pa Path) Index(i int) PathStep {
|
||||
if i < 0 {
|
||||
i = len(pa) + i
|
||||
}
|
||||
if i < 0 || i >= len(pa) {
|
||||
return pathStep{}
|
||||
}
|
||||
return pa[i]
|
||||
}
|
||||
|
||||
// String returns the simplified path to a node.
|
||||
// The simplified path only contains struct field accesses.
|
||||
//
|
||||
// For example:
|
||||
// MyMap.MySlices.MyField
|
||||
func (pa Path) String() string {
|
||||
var ss []string
|
||||
for _, s := range pa {
|
||||
if _, ok := s.(*structField); ok {
|
||||
ss = append(ss, s.String())
|
||||
}
|
||||
}
|
||||
return strings.TrimPrefix(strings.Join(ss, ""), ".")
|
||||
}
|
||||
|
||||
// GoString returns the path to a specific node using Go syntax.
|
||||
//
|
||||
// For example:
|
||||
// (*root.MyMap["key"].(*mypkg.MyStruct).MySlices)[2][3].MyField
|
||||
func (pa Path) GoString() string {
|
||||
var ssPre, ssPost []string
|
||||
var numIndirect int
|
||||
for i, s := range pa {
|
||||
var nextStep PathStep
|
||||
if i+1 < len(pa) {
|
||||
nextStep = pa[i+1]
|
||||
}
|
||||
switch s := s.(type) {
|
||||
case *indirect:
|
||||
numIndirect++
|
||||
pPre, pPost := "(", ")"
|
||||
switch nextStep.(type) {
|
||||
case *indirect:
|
||||
continue // Next step is indirection, so let them batch up
|
||||
case *structField:
|
||||
numIndirect-- // Automatic indirection on struct fields
|
||||
case nil:
|
||||
pPre, pPost = "", "" // Last step; no need for parenthesis
|
||||
}
|
||||
if numIndirect > 0 {
|
||||
ssPre = append(ssPre, pPre+strings.Repeat("*", numIndirect))
|
||||
ssPost = append(ssPost, pPost)
|
||||
}
|
||||
numIndirect = 0
|
||||
continue
|
||||
case *transform:
|
||||
ssPre = append(ssPre, s.trans.name+"(")
|
||||
ssPost = append(ssPost, ")")
|
||||
continue
|
||||
case *typeAssertion:
|
||||
// As a special-case, elide type assertions on anonymous types
|
||||
// since they are typically generated dynamically and can be very
|
||||
// verbose. For example, some transforms return interface{} because
|
||||
// of Go's lack of generics, but typically take in and return the
|
||||
// exact same concrete type.
|
||||
if s.Type().PkgPath() == "" {
|
||||
continue
|
||||
}
|
||||
}
|
||||
ssPost = append(ssPost, s.String())
|
||||
}
|
||||
for i, j := 0, len(ssPre)-1; i < j; i, j = i+1, j-1 {
|
||||
ssPre[i], ssPre[j] = ssPre[j], ssPre[i]
|
||||
}
|
||||
return strings.Join(ssPre, "") + strings.Join(ssPost, "")
|
||||
}
|
||||
|
||||
type (
|
||||
pathStep struct {
|
||||
typ reflect.Type
|
||||
}
|
||||
|
||||
sliceIndex struct {
|
||||
pathStep
|
||||
xkey, ykey int
|
||||
}
|
||||
mapIndex struct {
|
||||
pathStep
|
||||
key reflect.Value
|
||||
}
|
||||
typeAssertion struct {
|
||||
pathStep
|
||||
}
|
||||
structField struct {
|
||||
pathStep
|
||||
name string
|
||||
idx int
|
||||
|
||||
// These fields are used for forcibly accessing an unexported field.
|
||||
// pvx, pvy, and field are only valid if unexported is true.
|
||||
unexported bool
|
||||
force bool // Forcibly allow visibility
|
||||
pvx, pvy reflect.Value // Parent values
|
||||
field reflect.StructField // Field information
|
||||
}
|
||||
indirect struct {
|
||||
pathStep
|
||||
}
|
||||
transform struct {
|
||||
pathStep
|
||||
trans *transformer
|
||||
}
|
||||
)
|
||||
|
||||
func (ps pathStep) Type() reflect.Type { return ps.typ }
|
||||
func (ps pathStep) String() string {
|
||||
if ps.typ == nil {
|
||||
return "<nil>"
|
||||
}
|
||||
s := ps.typ.String()
|
||||
if s == "" || strings.ContainsAny(s, "{}\n") {
|
||||
return "root" // Type too simple or complex to print
|
||||
}
|
||||
return fmt.Sprintf("{%s}", s)
|
||||
}
|
||||
|
||||
func (si sliceIndex) String() string {
|
||||
switch {
|
||||
case si.xkey == si.ykey:
|
||||
return fmt.Sprintf("[%d]", si.xkey)
|
||||
case si.ykey == -1:
|
||||
// [5->?] means "I don't know where X[5] went"
|
||||
return fmt.Sprintf("[%d->?]", si.xkey)
|
||||
case si.xkey == -1:
|
||||
// [?->3] means "I don't know where Y[3] came from"
|
||||
return fmt.Sprintf("[?->%d]", si.ykey)
|
||||
default:
|
||||
// [5->3] means "X[5] moved to Y[3]"
|
||||
return fmt.Sprintf("[%d->%d]", si.xkey, si.ykey)
|
||||
}
|
||||
}
|
||||
func (mi mapIndex) String() string { return fmt.Sprintf("[%#v]", mi.key) }
|
||||
func (ta typeAssertion) String() string { return fmt.Sprintf(".(%v)", ta.typ) }
|
||||
func (sf structField) String() string { return fmt.Sprintf(".%s", sf.name) }
|
||||
func (in indirect) String() string { return "*" }
|
||||
func (tf transform) String() string { return fmt.Sprintf("%s()", tf.trans.name) }
|
||||
|
||||
func (si sliceIndex) Key() int {
|
||||
if si.xkey != si.ykey {
|
||||
return -1
|
||||
}
|
||||
return si.xkey
|
||||
}
|
||||
func (si sliceIndex) SplitKeys() (x, y int) { return si.xkey, si.ykey }
|
||||
func (mi mapIndex) Key() reflect.Value { return mi.key }
|
||||
func (sf structField) Name() string { return sf.name }
|
||||
func (sf structField) Index() int { return sf.idx }
|
||||
func (tf transform) Name() string { return tf.trans.name }
|
||||
func (tf transform) Func() reflect.Value { return tf.trans.fnc }
|
||||
func (tf transform) Option() Option { return tf.trans }
|
||||
|
||||
func (pathStep) isPathStep() {}
|
||||
func (sliceIndex) isSliceIndex() {}
|
||||
func (mapIndex) isMapIndex() {}
|
||||
func (typeAssertion) isTypeAssertion() {}
|
||||
func (structField) isStructField() {}
|
||||
func (indirect) isIndirect() {}
|
||||
func (transform) isTransform() {}
|
||||
|
||||
var (
|
||||
_ SliceIndex = sliceIndex{}
|
||||
_ MapIndex = mapIndex{}
|
||||
_ TypeAssertion = typeAssertion{}
|
||||
_ StructField = structField{}
|
||||
_ Indirect = indirect{}
|
||||
_ Transform = transform{}
|
||||
|
||||
_ PathStep = sliceIndex{}
|
||||
_ PathStep = mapIndex{}
|
||||
_ PathStep = typeAssertion{}
|
||||
_ PathStep = structField{}
|
||||
_ PathStep = indirect{}
|
||||
_ PathStep = transform{}
|
||||
)
|
||||
|
||||
// isExported reports whether the identifier is exported.
|
||||
func isExported(id string) bool {
|
||||
r, _ := utf8.DecodeRuneInString(id)
|
||||
return unicode.IsUpper(r)
|
||||
}
|
||||
|
||||
// isValid reports whether the identifier is valid.
|
||||
// Empty and underscore-only strings are not valid.
|
||||
func isValid(id string) bool {
|
||||
ok := id != "" && id != "_"
|
||||
for j, c := range id {
|
||||
ok = ok && (j > 0 || !unicode.IsDigit(c))
|
||||
ok = ok && (c == '_' || unicode.IsLetter(c) || unicode.IsDigit(c))
|
||||
}
|
||||
return ok
|
||||
}
|
53
vendor/github.com/google/go-cmp/cmp/reporter.go
generated
vendored
Normal file
53
vendor/github.com/google/go-cmp/cmp/reporter.go
generated
vendored
Normal file
|
@ -0,0 +1,53 @@
|
|||
// Copyright 2017, The Go Authors. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE.md file.
|
||||
|
||||
package cmp
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"reflect"
|
||||
"strings"
|
||||
|
||||
"github.com/google/go-cmp/cmp/internal/value"
|
||||
)
|
||||
|
||||
type defaultReporter struct {
|
||||
Option
|
||||
diffs []string // List of differences, possibly truncated
|
||||
ndiffs int // Total number of differences
|
||||
nbytes int // Number of bytes in diffs
|
||||
nlines int // Number of lines in diffs
|
||||
}
|
||||
|
||||
var _ reporter = (*defaultReporter)(nil)
|
||||
|
||||
func (r *defaultReporter) Report(x, y reflect.Value, eq bool, p Path) {
|
||||
if eq {
|
||||
return // Ignore equal results
|
||||
}
|
||||
const maxBytes = 4096
|
||||
const maxLines = 256
|
||||
r.ndiffs++
|
||||
if r.nbytes < maxBytes && r.nlines < maxLines {
|
||||
sx := value.Format(x, value.FormatConfig{UseStringer: true})
|
||||
sy := value.Format(y, value.FormatConfig{UseStringer: true})
|
||||
if sx == sy {
|
||||
// Unhelpful output, so use more exact formatting.
|
||||
sx = value.Format(x, value.FormatConfig{PrintPrimitiveType: true})
|
||||
sy = value.Format(y, value.FormatConfig{PrintPrimitiveType: true})
|
||||
}
|
||||
s := fmt.Sprintf("%#v:\n\t-: %s\n\t+: %s\n", p, sx, sy)
|
||||
r.diffs = append(r.diffs, s)
|
||||
r.nbytes += len(s)
|
||||
r.nlines += strings.Count(s, "\n")
|
||||
}
|
||||
}
|
||||
|
||||
func (r *defaultReporter) String() string {
|
||||
s := strings.Join(r.diffs, "")
|
||||
if r.ndiffs == len(r.diffs) {
|
||||
return s
|
||||
}
|
||||
return fmt.Sprintf("%s... %d more differences ...", s, r.ndiffs-len(r.diffs))
|
||||
}
|
15
vendor/github.com/google/go-cmp/cmp/unsafe_panic.go
generated
vendored
Normal file
15
vendor/github.com/google/go-cmp/cmp/unsafe_panic.go
generated
vendored
Normal file
|
@ -0,0 +1,15 @@
|
|||
// Copyright 2017, The Go Authors. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE.md file.
|
||||
|
||||
// +build purego appengine js
|
||||
|
||||
package cmp
|
||||
|
||||
import "reflect"
|
||||
|
||||
const supportAllowUnexported = false
|
||||
|
||||
func unsafeRetrieveField(reflect.Value, reflect.StructField) reflect.Value {
|
||||
panic("unsafeRetrieveField is not implemented")
|
||||
}
|
23
vendor/github.com/google/go-cmp/cmp/unsafe_reflect.go
generated
vendored
Normal file
23
vendor/github.com/google/go-cmp/cmp/unsafe_reflect.go
generated
vendored
Normal file
|
@ -0,0 +1,23 @@
|
|||
// Copyright 2017, The Go Authors. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE.md file.
|
||||
|
||||
// +build !purego,!appengine,!js
|
||||
|
||||
package cmp
|
||||
|
||||
import (
|
||||
"reflect"
|
||||
"unsafe"
|
||||
)
|
||||
|
||||
const supportAllowUnexported = true
|
||||
|
||||
// unsafeRetrieveField uses unsafe to forcibly retrieve any field from a struct
|
||||
// such that the value has read-write permissions.
|
||||
//
|
||||
// The parent struct, v, must be addressable, while f must be a StructField
|
||||
// describing the field to retrieve.
|
||||
func unsafeRetrieveField(v reflect.Value, f reflect.StructField) reflect.Value {
|
||||
return reflect.NewAt(f.Type, unsafe.Pointer(v.UnsafeAddr()+f.Offset)).Elem()
|
||||
}
|
10
vendor/github.com/mitchellh/go-homedir/homedir.go
generated
vendored
10
vendor/github.com/mitchellh/go-homedir/homedir.go
generated
vendored
|
@ -76,6 +76,16 @@ func Expand(path string) (string, error) {
|
|||
return filepath.Join(dir, path[1:]), nil
|
||||
}
|
||||
|
||||
// Reset clears the cache, forcing the next call to Dir to re-detect
|
||||
// the home directory. This generally never has to be called, but can be
|
||||
// useful in tests if you're modifying the home directory via the HOME
|
||||
// env var or something.
|
||||
func Reset() {
|
||||
cacheLock.Lock()
|
||||
defer cacheLock.Unlock()
|
||||
homedirCache = ""
|
||||
}
|
||||
|
||||
func dirUnix() (string, error) {
|
||||
homeEnv := "HOME"
|
||||
if runtime.GOOS == "plan9" {
|
||||
|
|
9
vendor/github.com/soniakeys/bits/.travis.yml
generated
vendored
Normal file
9
vendor/github.com/soniakeys/bits/.travis.yml
generated
vendored
Normal file
|
@ -0,0 +1,9 @@
|
|||
sudo: false
|
||||
language: go
|
||||
go: master
|
||||
before_script:
|
||||
- go vet
|
||||
- go get github.com/client9/misspell/cmd/misspell
|
||||
- misspell -error *
|
||||
- go get github.com/soniakeys/vetc
|
||||
- vetc
|
463
vendor/github.com/soniakeys/bits/bits.go
generated
vendored
Normal file
463
vendor/github.com/soniakeys/bits/bits.go
generated
vendored
Normal file
|
@ -0,0 +1,463 @@
|
|||
// Copyright 2017 Sonia Keys
|
||||
// License MIT: http://opensource.org/licenses/MIT
|
||||
|
||||
// Bits implements methods on a bit array type.
|
||||
//
|
||||
// The Bits type holds a fixed size array of bits, numbered consecutively
|
||||
// from zero. Some set-like operations are possible, but the API is more
|
||||
// array-like or register-like.
|
||||
package bits
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
mb "math/bits"
|
||||
)
|
||||
|
||||
// Bits holds a fixed number of bits.
|
||||
//
|
||||
// Bit number 0 is stored in the LSB, or bit 0, of the word indexed at 0.
|
||||
//
|
||||
// When Num is not a multiple of 64, the last element of Bits will hold some
|
||||
// bits beyond Num. These bits are undefined. They are not required to be
|
||||
// zero but do not have any meaning. Bits methods are not required to leave
|
||||
// them undisturbed.
|
||||
type Bits struct {
|
||||
Num int // number of bits
|
||||
Bits []uint64
|
||||
}
|
||||
|
||||
// New constructs a Bits value with the given number of bits.
|
||||
//
|
||||
// It panics if num is negative.
|
||||
func New(num int) Bits {
|
||||
if num < 0 {
|
||||
panic("negative number of bits")
|
||||
}
|
||||
return Bits{num, make([]uint64, (num+63)>>6)}
|
||||
}
|
||||
|
||||
// NewGivens constructs a Bits value with the given bits nums set to 1.
|
||||
//
|
||||
// The number of bits will be just enough to hold the largest bit value
|
||||
// listed. That is, the number of bits will be the max bit number plus one.
|
||||
//
|
||||
// It panics if any bit number is negative.
|
||||
func NewGivens(nums ...int) Bits {
|
||||
max := -1
|
||||
for _, p := range nums {
|
||||
if p > max {
|
||||
max = p
|
||||
}
|
||||
}
|
||||
b := New(max + 1)
|
||||
for _, p := range nums {
|
||||
b.SetBit(p, 1)
|
||||
}
|
||||
return b
|
||||
}
|
||||
|
||||
// AllOnes returns true if all Num bits are 1.
|
||||
func (b Bits) AllOnes() bool {
|
||||
last := len(b.Bits) - 1
|
||||
for _, w := range b.Bits[:last] {
|
||||
if w != ^uint64(0) {
|
||||
return false
|
||||
}
|
||||
}
|
||||
return ^b.Bits[last]<<uint(64*len(b.Bits)-b.Num) == 0
|
||||
}
|
||||
|
||||
// AllZeros returns true if all Num bits are 0.
|
||||
func (b Bits) AllZeros() bool {
|
||||
last := len(b.Bits) - 1
|
||||
for _, w := range b.Bits[:last] {
|
||||
if w != 0 {
|
||||
return false
|
||||
}
|
||||
}
|
||||
return b.Bits[last]<<uint(64*len(b.Bits)-b.Num) == 0
|
||||
}
|
||||
|
||||
// And sets z = x & y.
|
||||
//
|
||||
// It panics if x and y do not have the same Num.
|
||||
func (z *Bits) And(x, y Bits) {
|
||||
if x.Num != y.Num {
|
||||
panic("arguments have different number of bits")
|
||||
}
|
||||
if z.Num != x.Num {
|
||||
*z = New(x.Num)
|
||||
}
|
||||
for i, w := range y.Bits {
|
||||
z.Bits[i] = x.Bits[i] & w
|
||||
}
|
||||
}
|
||||
|
||||
// AndNot sets z = x &^ y.
|
||||
//
|
||||
// It panics if x and y do not have the same Num.
|
||||
func (z *Bits) AndNot(x, y Bits) {
|
||||
if x.Num != y.Num {
|
||||
panic("arguments have different number of bits")
|
||||
}
|
||||
if z.Num != x.Num {
|
||||
*z = New(x.Num)
|
||||
}
|
||||
for i, w := range y.Bits {
|
||||
z.Bits[i] = x.Bits[i] &^ w
|
||||
}
|
||||
}
|
||||
|
||||
// Bit returns the value of the n'th bit of receiver b.
|
||||
func (b Bits) Bit(n int) int {
|
||||
if n < 0 || n >= b.Num {
|
||||
panic("bit number out of range")
|
||||
}
|
||||
return int(b.Bits[n>>6] >> uint(n&63) & 1)
|
||||
}
|
||||
|
||||
// ClearAll sets all bits to 0.
|
||||
func (b Bits) ClearAll() {
|
||||
for i := range b.Bits {
|
||||
b.Bits[i] = 0
|
||||
}
|
||||
}
|
||||
|
||||
// ClearBits sets the given bits to 0 in receiver b.
|
||||
//
|
||||
// Other bits of b are left unchanged.
|
||||
//
|
||||
// It panics if any bit number is out of range.
|
||||
// That is, negative or >= the number of bits.
|
||||
func (b Bits) ClearBits(nums ...int) {
|
||||
for _, p := range nums {
|
||||
b.SetBit(p, 0)
|
||||
}
|
||||
}
|
||||
|
||||
// Equal returns true if all Num bits of a and b are equal.
|
||||
//
|
||||
// It panics if a and b have different Num.
|
||||
func (a Bits) Equal(b Bits) bool {
|
||||
if a.Num != b.Num {
|
||||
panic("receiver and argument have different number of bits")
|
||||
}
|
||||
if a.Num == 0 {
|
||||
return true
|
||||
}
|
||||
last := len(a.Bits) - 1
|
||||
for i, w := range a.Bits[:last] {
|
||||
if w != b.Bits[i] {
|
||||
return false
|
||||
}
|
||||
}
|
||||
return (a.Bits[last]^b.Bits[last])<<uint(len(a.Bits)*64-a.Num) == 0
|
||||
}
|
||||
|
||||
// IterateOnes calls visitor function v for each bit with a value of 1, in order
|
||||
// from lowest bit to highest bit.
|
||||
//
|
||||
// Iteration continues to the highest bit as long as v returns true.
|
||||
// It stops if v returns false.
|
||||
//
|
||||
// IterateOnes returns true normally. It returns false if v returns false.
|
||||
//
|
||||
// IterateOnes may not be sensitive to changes if bits are changed during
|
||||
// iteration, by the vistor function for example.
|
||||
// See OneFrom for an iteration method sensitive to changes during iteration.
|
||||
func (b Bits) IterateOnes(v func(int) bool) bool {
|
||||
for x, w := range b.Bits {
|
||||
if w != 0 {
|
||||
t := mb.TrailingZeros64(w)
|
||||
i := t // index in w of next 1 bit
|
||||
for {
|
||||
n := x<<6 | i
|
||||
if n >= b.Num {
|
||||
return true
|
||||
}
|
||||
if !v(x<<6 | i) {
|
||||
return false
|
||||
}
|
||||
w >>= uint(t + 1)
|
||||
if w == 0 {
|
||||
break
|
||||
}
|
||||
t = mb.TrailingZeros64(w)
|
||||
i += 1 + t
|
||||
}
|
||||
}
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
// IterateZeros calls visitor function v for each bit with a value of 0,
|
||||
// in order from lowest bit to highest bit.
|
||||
//
|
||||
// Iteration continues to the highest bit as long as v returns true.
|
||||
// It stops if v returns false.
|
||||
//
|
||||
// IterateZeros returns true normally. It returns false if v returns false.
|
||||
//
|
||||
// IterateZeros may not be sensitive to changes if bits are changed during
|
||||
// iteration, by the vistor function for example.
|
||||
// See ZeroFrom for an iteration method sensitive to changes during iteration.
|
||||
func (b Bits) IterateZeros(v func(int) bool) bool {
|
||||
for x, w := range b.Bits {
|
||||
w = ^w
|
||||
if w != 0 {
|
||||
t := mb.TrailingZeros64(w)
|
||||
i := t // index in w of next 1 bit
|
||||
for {
|
||||
n := x<<6 | i
|
||||
if n >= b.Num {
|
||||
return true
|
||||
}
|
||||
if !v(x<<6 | i) {
|
||||
return false
|
||||
}
|
||||
w >>= uint(t + 1)
|
||||
if w == 0 {
|
||||
break
|
||||
}
|
||||
t = mb.TrailingZeros64(w)
|
||||
i += 1 + t
|
||||
}
|
||||
}
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
// Not sets receiver z to the complement of b.
|
||||
func (z *Bits) Not(b Bits) {
|
||||
if z.Num != b.Num {
|
||||
*z = New(b.Num)
|
||||
}
|
||||
for i, w := range b.Bits {
|
||||
z.Bits[i] = ^w
|
||||
}
|
||||
}
|
||||
|
||||
// OneFrom returns the number of the first 1 bit at or after (from) bit num.
|
||||
//
|
||||
// It returns -1 if there is no one bit at or after num.
|
||||
//
|
||||
// This provides one way to iterate over one bits.
|
||||
// To iterate over the one bits, call OneFrom with n = 0 to get the the first
|
||||
// one bit, then call with the result + 1 to get successive one bits.
|
||||
// Unlike the Iterate method, this technique is stateless and so allows
|
||||
// bits to be changed between successive calls.
|
||||
//
|
||||
// There is no panic for calling OneFrom with an argument >= b.Num.
|
||||
// In this case OneFrom simply returns -1.
|
||||
//
|
||||
// See also Iterate.
|
||||
func (b Bits) OneFrom(num int) int {
|
||||
if num >= b.Num {
|
||||
return -1
|
||||
}
|
||||
x := num >> 6
|
||||
// test for 1 in this word at or after n
|
||||
if wx := b.Bits[x] >> uint(num&63); wx != 0 {
|
||||
num += mb.TrailingZeros64(wx)
|
||||
if num >= b.Num {
|
||||
return -1
|
||||
}
|
||||
return num
|
||||
}
|
||||
x++
|
||||
for y, wy := range b.Bits[x:] {
|
||||
if wy != 0 {
|
||||
num = (x+y)<<6 | mb.TrailingZeros64(wy)
|
||||
if num >= b.Num {
|
||||
return -1
|
||||
}
|
||||
return num
|
||||
}
|
||||
}
|
||||
return -1
|
||||
}
|
||||
|
||||
// Or sets z = x | y.
|
||||
//
|
||||
// It panics if x and y do not have the same Num.
|
||||
func (z *Bits) Or(x, y Bits) {
|
||||
if x.Num != y.Num {
|
||||
panic("arguments have different number of bits")
|
||||
}
|
||||
if z.Num != x.Num {
|
||||
*z = New(x.Num)
|
||||
}
|
||||
for i, w := range y.Bits {
|
||||
z.Bits[i] = x.Bits[i] | w
|
||||
}
|
||||
}
|
||||
|
||||
// OnesCount returns the number of 1 bits.
|
||||
func (b Bits) OnesCount() (c int) {
|
||||
if b.Num == 0 {
|
||||
return 0
|
||||
}
|
||||
last := len(b.Bits) - 1
|
||||
for _, w := range b.Bits[:last] {
|
||||
c += mb.OnesCount64(w)
|
||||
}
|
||||
c += mb.OnesCount64(b.Bits[last] << uint(len(b.Bits)*64-b.Num))
|
||||
return
|
||||
}
|
||||
|
||||
// Set sets the bits of z to the bits of x.
|
||||
func (z *Bits) Set(b Bits) {
|
||||
if z.Num != b.Num {
|
||||
*z = New(b.Num)
|
||||
}
|
||||
copy(z.Bits, b.Bits)
|
||||
}
|
||||
|
||||
// SetAll sets z to have all 1 bits.
|
||||
func (b Bits) SetAll() {
|
||||
for i := range b.Bits {
|
||||
b.Bits[i] = ^uint64(0)
|
||||
}
|
||||
}
|
||||
|
||||
// SetBit sets the n'th bit to x, where x is a 0 or 1.
|
||||
//
|
||||
// It panics if n is out of range
|
||||
func (b Bits) SetBit(n, x int) {
|
||||
if n < 0 || n >= b.Num {
|
||||
panic("bit number out of range")
|
||||
}
|
||||
if x == 0 {
|
||||
b.Bits[n>>6] &^= 1 << uint(n&63)
|
||||
} else {
|
||||
b.Bits[n>>6] |= 1 << uint(n&63)
|
||||
}
|
||||
}
|
||||
|
||||
// SetBits sets the given bits to 1 in receiver b.
|
||||
//
|
||||
// Other bits of b are left unchanged.
|
||||
//
|
||||
// It panics if any bit number is out of range, negative or >= the number
|
||||
// of bits.
|
||||
func (b Bits) SetBits(nums ...int) {
|
||||
for _, p := range nums {
|
||||
b.SetBit(p, 1)
|
||||
}
|
||||
}
|
||||
|
||||
// Single returns true if b has exactly one 1 bit.
|
||||
func (b Bits) Single() bool {
|
||||
// like OnesCount, but stop as soon as two are found
|
||||
if b.Num == 0 {
|
||||
return false
|
||||
}
|
||||
c := 0
|
||||
last := len(b.Bits) - 1
|
||||
for _, w := range b.Bits[:last] {
|
||||
c += mb.OnesCount64(w)
|
||||
if c > 1 {
|
||||
return false
|
||||
}
|
||||
}
|
||||
c += mb.OnesCount64(b.Bits[last] << uint(len(b.Bits)*64-b.Num))
|
||||
return c == 1
|
||||
}
|
||||
|
||||
// Slice returns a slice with the bit numbers of each 1 bit.
|
||||
func (b Bits) Slice() (s []int) {
|
||||
for x, w := range b.Bits {
|
||||
if w == 0 {
|
||||
continue
|
||||
}
|
||||
t := mb.TrailingZeros64(w)
|
||||
i := t // index in w of next 1 bit
|
||||
for {
|
||||
n := x<<6 | i
|
||||
if n >= b.Num {
|
||||
break
|
||||
}
|
||||
s = append(s, n)
|
||||
w >>= uint(t + 1)
|
||||
if w == 0 {
|
||||
break
|
||||
}
|
||||
t = mb.TrailingZeros64(w)
|
||||
i += 1 + t
|
||||
}
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
// String returns a readable representation.
|
||||
//
|
||||
// The returned string is big-endian, with the highest number bit first.
|
||||
//
|
||||
// If Num is 0, an empty string is returned.
|
||||
func (b Bits) String() (s string) {
|
||||
if b.Num == 0 {
|
||||
return ""
|
||||
}
|
||||
last := len(b.Bits) - 1
|
||||
for _, w := range b.Bits[:last] {
|
||||
s = fmt.Sprintf("%064b", w) + s
|
||||
}
|
||||
lb := b.Num - 64*last
|
||||
return fmt.Sprintf("%0*b", lb,
|
||||
b.Bits[last]&(^uint64(0)>>uint(64-lb))) + s
|
||||
}
|
||||
|
||||
// Xor sets z = x ^ y.
|
||||
func (z *Bits) Xor(x, y Bits) {
|
||||
if x.Num != y.Num {
|
||||
panic("arguments have different number of bits")
|
||||
}
|
||||
if z.Num != x.Num {
|
||||
*z = New(x.Num)
|
||||
}
|
||||
for i, w := range y.Bits {
|
||||
z.Bits[i] = x.Bits[i] ^ w
|
||||
}
|
||||
}
|
||||
|
||||
// ZeroFrom returns the number of the first 0 bit at or after (from) bit num.
|
||||
//
|
||||
// It returns -1 if there is no zero bit at or after num.
|
||||
//
|
||||
// This provides one way to iterate over zero bits.
|
||||
// To iterate over the zero bits, call ZeroFrom with n = 0 to get the the first
|
||||
// zero bit, then call with the result + 1 to get successive zero bits.
|
||||
// Unlike the IterateZeros method, this technique is stateless and so allows
|
||||
// bits to be changed between successive calls.
|
||||
//
|
||||
// There is no panic for calling ZeroFrom with an argument >= b.Num.
|
||||
// In this case ZeroFrom simply returns -1.
|
||||
//
|
||||
// See also IterateZeros.
|
||||
func (b Bits) ZeroFrom(num int) int {
|
||||
// code much like OneFrom except words are negated before testing
|
||||
if num >= b.Num {
|
||||
return -1
|
||||
}
|
||||
x := num >> 6
|
||||
// negate word to test for 0 at or after n
|
||||
if wx := ^b.Bits[x] >> uint(num&63); wx != 0 {
|
||||
num += mb.TrailingZeros64(wx)
|
||||
if num >= b.Num {
|
||||
return -1
|
||||
}
|
||||
return num
|
||||
}
|
||||
x++
|
||||
for y, wy := range b.Bits[x:] {
|
||||
wy = ^wy
|
||||
if wy != 0 {
|
||||
num = (x+y)<<6 | mb.TrailingZeros64(wy)
|
||||
if num >= b.Num {
|
||||
return -1
|
||||
}
|
||||
return num
|
||||
}
|
||||
}
|
||||
return -1
|
||||
}
|
1
vendor/github.com/soniakeys/bits/go.mod
generated
vendored
Normal file
1
vendor/github.com/soniakeys/bits/go.mod
generated
vendored
Normal file
|
@ -0,0 +1 @@
|
|||
module "github.com/soniakeys/bits"
|
38
vendor/github.com/soniakeys/bits/readme.adoc
generated
vendored
Normal file
38
vendor/github.com/soniakeys/bits/readme.adoc
generated
vendored
Normal file
|
@ -0,0 +1,38 @@
|
|||
= Bits
|
||||
|
||||
Bits provides methods on a bit array type.
|
||||
|
||||
The Bits type holds a fixed size array of bits, numbered consecutively
|
||||
from zero. Some set-like operations are possible, but the API is more
|
||||
array-like or register-like.
|
||||
|
||||
image:https://godoc.org/github.com/soniakeys/bits?status.svg[link=https://godoc.org/github.com/soniakeys/bits] image:https://travis-ci.org/soniakeys/bits.svg[link=https://travis-ci.org/soniakeys/bits]
|
||||
|
||||
== Motivation and history
|
||||
|
||||
This package evolved from needs of my library of
|
||||
https://github.com/soniakeys/graph[graph algorithms]. For graph algorithms
|
||||
a common need is to store a single bit of information per node in a way that
|
||||
is both fast and memory efficient. I began by using `big.Int` from the standard
|
||||
library, then wrapped big.Int in a type. From time to time I considered
|
||||
other publicly available bit array or bit set packages, such as Will
|
||||
Fitzgerald's popular https://github.com/willf/bitset[bitset], but there were
|
||||
always little reasons I preferred my own type and methods. My type that
|
||||
wrapped `big.Int` met my needs until some simple benchmarks indicated it
|
||||
might be causing performance problems. Some further experiments supported
|
||||
this hypothesis so I ran further tests with a prototype bit array written
|
||||
from scratch. Then satisfied that my custom bit array was solving the graph
|
||||
performance problems, I decided to move it to a separate package with the
|
||||
idea it might have more general utility. For the initial version of this
|
||||
package I did the following:
|
||||
|
||||
- implemented a few tests to demonstrate fundamental correctness
|
||||
- brought over most methods of my type that wrapped big.Int
|
||||
- changed the index type from the graph-specific node index to a general `int`
|
||||
- replaced some custom bit-twiddling with use of the new `math/bits` package
|
||||
in the standard library
|
||||
- renamed a few methods for clarity
|
||||
- added a few methods for symmetry
|
||||
- added a few new methods I had seen a need for in my graph library
|
||||
- added doc, examples, tests, and more tests for 100% coverage
|
||||
- added this readme
|
2
vendor/github.com/soniakeys/graph/.gitignore
generated
vendored
Normal file
2
vendor/github.com/soniakeys/graph/.gitignore
generated
vendored
Normal file
|
@ -0,0 +1,2 @@
|
|||
*.dot
|
||||
anecdote/anecdote
|
11
vendor/github.com/soniakeys/graph/.travis.yml
generated
vendored
Normal file
11
vendor/github.com/soniakeys/graph/.travis.yml
generated
vendored
Normal file
|
@ -0,0 +1,11 @@
|
|||
sudo: false
|
||||
language: go
|
||||
go:
|
||||
- "1.9.x"
|
||||
- master
|
||||
before_script:
|
||||
- go tool vet -composites=false -printf=false -shift=false .
|
||||
- go get github.com/client9/misspell/cmd/misspell
|
||||
- go get github.com/soniakeys/vetc
|
||||
- misspell -error * */* */*/*
|
||||
- vetc
|
406
vendor/github.com/soniakeys/graph/adj.go
generated
vendored
Normal file
406
vendor/github.com/soniakeys/graph/adj.go
generated
vendored
Normal file
|
@ -0,0 +1,406 @@
|
|||
// Copyright 2014 Sonia Keys
|
||||
// License MIT: https://opensource.org/licenses/MIT
|
||||
|
||||
package graph
|
||||
|
||||
// adj.go contains methods on AdjacencyList and LabeledAdjacencyList.
|
||||
//
|
||||
// AdjacencyList methods are placed first and are alphabetized.
|
||||
// LabeledAdjacencyList methods follow, also alphabetized.
|
||||
// Only exported methods need be alphabetized; non-exported methods can
|
||||
// be left near their use.
|
||||
|
||||
import (
|
||||
"sort"
|
||||
|
||||
"github.com/soniakeys/bits"
|
||||
)
|
||||
|
||||
// NI is a "node int"
|
||||
//
|
||||
// It is a node number or node ID. NIs are used extensively as slice indexes.
|
||||
// NIs typically account for a significant fraction of the memory footprint of
|
||||
// a graph.
|
||||
type NI int32
|
||||
|
||||
// AnyParallel identifies if a graph contains parallel arcs, multiple arcs
|
||||
// that lead from a node to the same node.
|
||||
//
|
||||
// If the graph has parallel arcs, the results fr and to represent an example
|
||||
// where there are parallel arcs from node `fr` to node `to`.
|
||||
//
|
||||
// If there are no parallel arcs, the method returns false -1 -1.
|
||||
//
|
||||
// Multiple loops on a node count as parallel arcs.
|
||||
//
|
||||
// See also alt.AnyParallelMap, which can perform better for some large
|
||||
// or dense graphs.
|
||||
func (g AdjacencyList) AnyParallel() (has bool, fr, to NI) {
|
||||
var t []NI
|
||||
for n, to := range g {
|
||||
if len(to) == 0 {
|
||||
continue
|
||||
}
|
||||
// different code in the labeled version, so no code gen.
|
||||
t = append(t[:0], to...)
|
||||
sort.Slice(t, func(i, j int) bool { return t[i] < t[j] })
|
||||
t0 := t[0]
|
||||
for _, to := range t[1:] {
|
||||
if to == t0 {
|
||||
return true, NI(n), t0
|
||||
}
|
||||
t0 = to
|
||||
}
|
||||
}
|
||||
return false, -1, -1
|
||||
}
|
||||
|
||||
// Complement returns the arc-complement of a simple graph.
|
||||
//
|
||||
// The result will have an arc for every pair of distinct nodes where there
|
||||
// is not an arc in g. The complement is valid for both directed and
|
||||
// undirected graphs. If g is undirected, the complement will be undirected.
|
||||
// The result will always be a simple graph, having no loops or parallel arcs.
|
||||
func (g AdjacencyList) Complement() AdjacencyList {
|
||||
c := make(AdjacencyList, len(g))
|
||||
b := bits.New(len(g))
|
||||
for n, to := range g {
|
||||
b.ClearAll()
|
||||
for _, to := range to {
|
||||
b.SetBit(int(to), 1)
|
||||
}
|
||||
b.SetBit(n, 1)
|
||||
ct := make([]NI, len(g)-b.OnesCount())
|
||||
i := 0
|
||||
b.IterateZeros(func(to int) bool {
|
||||
ct[i] = NI(to)
|
||||
i++
|
||||
return true
|
||||
})
|
||||
c[n] = ct
|
||||
}
|
||||
return c
|
||||
}
|
||||
|
||||
// IsUndirected returns true if g represents an undirected graph.
|
||||
//
|
||||
// Returns true when all non-loop arcs are paired in reciprocal pairs.
|
||||
// Otherwise returns false and an example unpaired arc.
|
||||
func (g AdjacencyList) IsUndirected() (u bool, from, to NI) {
|
||||
// similar code in dot/writeUndirected
|
||||
unpaired := make(AdjacencyList, len(g))
|
||||
for fr, to := range g {
|
||||
arc: // for each arc in g
|
||||
for _, to := range to {
|
||||
if to == NI(fr) {
|
||||
continue // loop
|
||||
}
|
||||
// search unpaired arcs
|
||||
ut := unpaired[to]
|
||||
for i, u := range ut {
|
||||
if u == NI(fr) { // found reciprocal
|
||||
last := len(ut) - 1
|
||||
ut[i] = ut[last]
|
||||
unpaired[to] = ut[:last]
|
||||
continue arc
|
||||
}
|
||||
}
|
||||
// reciprocal not found
|
||||
unpaired[fr] = append(unpaired[fr], to)
|
||||
}
|
||||
}
|
||||
for fr, to := range unpaired {
|
||||
if len(to) > 0 {
|
||||
return false, NI(fr), to[0]
|
||||
}
|
||||
}
|
||||
return true, -1, -1
|
||||
}
|
||||
|
||||
// SortArcLists sorts the arc lists of each node of receiver g.
|
||||
//
|
||||
// Nodes are not relabeled and the graph remains equivalent.
|
||||
func (g AdjacencyList) SortArcLists() {
|
||||
for _, to := range g {
|
||||
sort.Slice(to, func(i, j int) bool { return to[i] < to[j] })
|
||||
}
|
||||
}
|
||||
|
||||
// ------- Labeled methods below -------
|
||||
|
||||
// ArcsAsEdges constructs an edge list with an edge for each arc, including
|
||||
// reciprocals.
|
||||
//
|
||||
// This is a simple way to construct an edge list for algorithms that allow
|
||||
// the duplication represented by the reciprocal arcs. (e.g. Kruskal)
|
||||
//
|
||||
// See also LabeledUndirected.Edges for the edge list without this duplication.
|
||||
func (g LabeledAdjacencyList) ArcsAsEdges() (el []LabeledEdge) {
|
||||
for fr, to := range g {
|
||||
for _, to := range to {
|
||||
el = append(el, LabeledEdge{Edge{NI(fr), to.To}, to.Label})
|
||||
}
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
// DistanceMatrix constructs a distance matrix corresponding to the arcs
|
||||
// of graph g and weight function w.
|
||||
//
|
||||
// An arc from f to t with WeightFunc return w is represented by d[f][t] == w.
|
||||
// In case of parallel arcs, the lowest weight is stored. The distance from
|
||||
// any node to itself d[n][n] is 0, unless the node has a loop with a negative
|
||||
// weight. If g has no arc from f to distinct t, +Inf is stored for d[f][t].
|
||||
//
|
||||
// The returned DistanceMatrix is suitable for DistanceMatrix.FloydWarshall.
|
||||
func (g LabeledAdjacencyList) DistanceMatrix(w WeightFunc) (d DistanceMatrix) {
|
||||
d = newDM(len(g))
|
||||
for fr, to := range g {
|
||||
for _, to := range to {
|
||||
// < to pick min of parallel arcs (also nicely ignores NaN)
|
||||
if wt := w(to.Label); wt < d[fr][to.To] {
|
||||
d[fr][to.To] = wt
|
||||
}
|
||||
}
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
// HasArcLabel returns true if g has any arc from node `fr` to node `to`
|
||||
// with label `l`.
|
||||
//
|
||||
// Also returned is the index within the slice of arcs from node `fr`.
|
||||
// If no arc from `fr` to `to` with label `l` is present, HasArcLabel returns
|
||||
// false, -1.
|
||||
func (g LabeledAdjacencyList) HasArcLabel(fr, to NI, l LI) (bool, int) {
|
||||
t := Half{to, l}
|
||||
for x, h := range g[fr] {
|
||||
if h == t {
|
||||
return true, x
|
||||
}
|
||||
}
|
||||
return false, -1
|
||||
}
|
||||
|
||||
// AnyParallel identifies if a graph contains parallel arcs, multiple arcs
|
||||
// that lead from a node to the same node.
|
||||
//
|
||||
// If the graph has parallel arcs, the results fr and to represent an example
|
||||
// where there are parallel arcs from node `fr` to node `to`.
|
||||
//
|
||||
// If there are no parallel arcs, the method returns -1 -1.
|
||||
//
|
||||
// Multiple loops on a node count as parallel arcs.
|
||||
//
|
||||
// See also alt.AnyParallelMap, which can perform better for some large
|
||||
// or dense graphs.
|
||||
func (g LabeledAdjacencyList) AnyParallel() (has bool, fr, to NI) {
|
||||
var t []NI
|
||||
for n, to := range g {
|
||||
if len(to) == 0 {
|
||||
continue
|
||||
}
|
||||
// slightly different code needed here compared to AdjacencyList
|
||||
t = t[:0]
|
||||
for _, to := range to {
|
||||
t = append(t, to.To)
|
||||
}
|
||||
sort.Slice(t, func(i, j int) bool { return t[i] < t[j] })
|
||||
t0 := t[0]
|
||||
for _, to := range t[1:] {
|
||||
if to == t0 {
|
||||
return true, NI(n), t0
|
||||
}
|
||||
t0 = to
|
||||
}
|
||||
}
|
||||
return false, -1, -1
|
||||
}
|
||||
|
||||
// AnyParallelLabel identifies if a graph contains parallel arcs with the same
|
||||
// label.
|
||||
//
|
||||
// If the graph has parallel arcs with the same label, the results fr and to
|
||||
// represent an example where there are parallel arcs from node `fr`
|
||||
// to node `to`.
|
||||
//
|
||||
// If there are no parallel arcs, the method returns false -1 Half{}.
|
||||
//
|
||||
// Multiple loops on a node count as parallel arcs.
|
||||
func (g LabeledAdjacencyList) AnyParallelLabel() (has bool, fr NI, to Half) {
|
||||
var t []Half
|
||||
for n, to := range g {
|
||||
if len(to) == 0 {
|
||||
continue
|
||||
}
|
||||
// slightly different code needed here compared to AdjacencyList
|
||||
t = t[:0]
|
||||
for _, to := range to {
|
||||
t = append(t, to)
|
||||
}
|
||||
sort.Slice(t, func(i, j int) bool {
|
||||
return t[i].To < t[j].To ||
|
||||
t[i].To == t[j].To && t[i].Label < t[j].Label
|
||||
})
|
||||
t0 := t[0]
|
||||
for _, to := range t[1:] {
|
||||
if to == t0 {
|
||||
return true, NI(n), t0
|
||||
}
|
||||
t0 = to
|
||||
}
|
||||
}
|
||||
return false, -1, Half{}
|
||||
}
|
||||
|
||||
// IsUndirected returns true if g represents an undirected graph.
|
||||
//
|
||||
// Returns true when all non-loop arcs are paired in reciprocal pairs with
|
||||
// matching labels. Otherwise returns false and an example unpaired arc.
|
||||
//
|
||||
// Note the requirement that reciprocal pairs have matching labels is
|
||||
// an additional test not present in the otherwise equivalent unlabeled version
|
||||
// of IsUndirected.
|
||||
func (g LabeledAdjacencyList) IsUndirected() (u bool, from NI, to Half) {
|
||||
// similar code in LabeledAdjacencyList.Edges
|
||||
unpaired := make(LabeledAdjacencyList, len(g))
|
||||
for fr, to := range g {
|
||||
arc: // for each arc in g
|
||||
for _, to := range to {
|
||||
if to.To == NI(fr) {
|
||||
continue // loop
|
||||
}
|
||||
// search unpaired arcs
|
||||
ut := unpaired[to.To]
|
||||
for i, u := range ut {
|
||||
if u.To == NI(fr) && u.Label == to.Label { // found reciprocal
|
||||
last := len(ut) - 1
|
||||
ut[i] = ut[last]
|
||||
unpaired[to.To] = ut[:last]
|
||||
continue arc
|
||||
}
|
||||
}
|
||||
// reciprocal not found
|
||||
unpaired[fr] = append(unpaired[fr], to)
|
||||
}
|
||||
}
|
||||
for fr, to := range unpaired {
|
||||
if len(to) > 0 {
|
||||
return false, NI(fr), to[0]
|
||||
}
|
||||
}
|
||||
return true, -1, to
|
||||
}
|
||||
|
||||
// ArcLabels constructs the multiset of LIs present in g.
|
||||
func (g LabeledAdjacencyList) ArcLabels() map[LI]int {
|
||||
s := map[LI]int{}
|
||||
for _, to := range g {
|
||||
for _, to := range to {
|
||||
s[to.Label]++
|
||||
}
|
||||
}
|
||||
return s
|
||||
}
|
||||
|
||||
// NegativeArc returns true if the receiver graph contains a negative arc.
|
||||
func (g LabeledAdjacencyList) NegativeArc(w WeightFunc) bool {
|
||||
for _, nbs := range g {
|
||||
for _, nb := range nbs {
|
||||
if w(nb.Label) < 0 {
|
||||
return true
|
||||
}
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
// ParallelArcsLabel identifies all arcs from node `fr` to node `to` with label `l`.
|
||||
//
|
||||
// The returned slice contains an element for each arc from node `fr` to node `to`
|
||||
// with label `l`. The element value is the index within the slice of arcs from node
|
||||
// `fr`.
|
||||
//
|
||||
// See also the method HasArcLabel, which stops after finding a single arc.
|
||||
func (g LabeledAdjacencyList) ParallelArcsLabel(fr, to NI, l LI) (p []int) {
|
||||
t := Half{to, l}
|
||||
for x, h := range g[fr] {
|
||||
if h == t {
|
||||
p = append(p, x)
|
||||
}
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
// Unlabeled constructs the unlabeled graph corresponding to g.
|
||||
func (g LabeledAdjacencyList) Unlabeled() AdjacencyList {
|
||||
a := make(AdjacencyList, len(g))
|
||||
for n, nbs := range g {
|
||||
to := make([]NI, len(nbs))
|
||||
for i, nb := range nbs {
|
||||
to[i] = nb.To
|
||||
}
|
||||
a[n] = to
|
||||
}
|
||||
return a
|
||||
}
|
||||
|
||||
// WeightedArcsAsEdges constructs a WeightedEdgeList object from the receiver.
|
||||
//
|
||||
// Internally it calls g.ArcsAsEdges() to obtain the Edges member.
|
||||
// See LabeledAdjacencyList.ArcsAsEdges().
|
||||
func (g LabeledAdjacencyList) WeightedArcsAsEdges(w WeightFunc) *WeightedEdgeList {
|
||||
return &WeightedEdgeList{
|
||||
Order: g.Order(),
|
||||
WeightFunc: w,
|
||||
Edges: g.ArcsAsEdges(),
|
||||
}
|
||||
}
|
||||
|
||||
// WeightedInDegree computes the weighted in-degree of each node in g
|
||||
// for a given weight function w.
|
||||
//
|
||||
// The weighted in-degree of a node is the sum of weights of arcs going to
|
||||
// the node.
|
||||
//
|
||||
// A weighted degree of a node is often termed the "strength" of a node.
|
||||
//
|
||||
// See note for undirected graphs at LabeledAdjacencyList.WeightedOutDegree.
|
||||
func (g LabeledAdjacencyList) WeightedInDegree(w WeightFunc) []float64 {
|
||||
ind := make([]float64, len(g))
|
||||
for _, to := range g {
|
||||
for _, to := range to {
|
||||
ind[to.To] += w(to.Label)
|
||||
}
|
||||
}
|
||||
return ind
|
||||
}
|
||||
|
||||
// WeightedOutDegree computes the weighted out-degree of the specified node
|
||||
// for a given weight function w.
|
||||
//
|
||||
// The weighted out-degree of a node is the sum of weights of arcs going from
|
||||
// the node.
|
||||
//
|
||||
// A weighted degree of a node is often termed the "strength" of a node.
|
||||
//
|
||||
// Note for undirected graphs, the WeightedOutDegree result for a node will
|
||||
// equal the WeightedInDegree for the node. You can use WeightedInDegree if
|
||||
// you have need for the weighted degrees of all nodes or use WeightedOutDegree
|
||||
// to compute the weighted degrees of individual nodes. In either case loops
|
||||
// are counted just once, unlike the (unweighted) UndirectedDegree methods.
|
||||
func (g LabeledAdjacencyList) WeightedOutDegree(n NI, w WeightFunc) (d float64) {
|
||||
for _, to := range g[n] {
|
||||
d += w(to.Label)
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
// More about loops and strength: I didn't see consensus on this especially
|
||||
// in the case of undirected graphs. Some sources said to add in-degree and
|
||||
// out-degree, which would seemingly double both loops and non-loops.
|
||||
// Some said to double loops. Some said sum the edge weights and had no
|
||||
// comment on loops. R of course makes everything an option. The meaning
|
||||
// of "strength" where loops exist is unclear. So while I could write an
|
||||
// UndirectedWeighted degree function that doubles loops but not edges,
|
||||
// I'm going to just leave this for now.
|
417
vendor/github.com/soniakeys/graph/adj_RO.go
generated
vendored
Normal file
417
vendor/github.com/soniakeys/graph/adj_RO.go
generated
vendored
Normal file
|
@ -0,0 +1,417 @@
|
|||
// Copyright 2014 Sonia Keys
|
||||
// License MIT: http://opensource.org/licenses/MIT
|
||||
|
||||
package graph
|
||||
|
||||
// adj_RO.go is code generated from adj_cg.go by directives in graph.go.
|
||||
// Editing adj_cg.go is okay.
|
||||
// DO NOT EDIT adj_RO.go. The RO is for Read Only.
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"math/rand"
|
||||
|
||||
"github.com/soniakeys/bits"
|
||||
)
|
||||
|
||||
// ArcDensity returns density for an simple directed graph.
|
||||
//
|
||||
// See also ArcDensity function.
|
||||
//
|
||||
// There are equivalent labeled and unlabeled versions of this method.
|
||||
func (g AdjacencyList) ArcDensity() float64 {
|
||||
return ArcDensity(len(g), g.ArcSize())
|
||||
}
|
||||
|
||||
// ArcSize returns the number of arcs in g.
|
||||
//
|
||||
// Note that for an undirected graph without loops, the number of undirected
|
||||
// edges -- the traditional meaning of graph size -- will be ArcSize()/2.
|
||||
// On the other hand, if g is an undirected graph that has or may have loops,
|
||||
// g.ArcSize()/2 is not a meaningful quantity.
|
||||
//
|
||||
// There are equivalent labeled and unlabeled versions of this method.
|
||||
func (g AdjacencyList) ArcSize() int {
|
||||
m := 0
|
||||
for _, to := range g {
|
||||
m += len(to)
|
||||
}
|
||||
return m
|
||||
}
|
||||
|
||||
// BoundsOk validates that all arcs in g stay within the slice bounds of g.
|
||||
//
|
||||
// BoundsOk returns true when no arcs point outside the bounds of g.
|
||||
// Otherwise it returns false and an example arc that points outside of g.
|
||||
//
|
||||
// Most methods of this package assume the BoundsOk condition and may
|
||||
// panic when they encounter an arc pointing outside of the graph. This
|
||||
// function can be used to validate a graph when the BoundsOk condition
|
||||
// is unknown.
|
||||
//
|
||||
// There are equivalent labeled and unlabeled versions of this method.
|
||||
func (g AdjacencyList) BoundsOk() (ok bool, fr NI, to NI) {
|
||||
for fr, to := range g {
|
||||
for _, to := range to {
|
||||
if to < 0 || to >= NI(len(g)) {
|
||||
return false, NI(fr), to
|
||||
}
|
||||
}
|
||||
}
|
||||
return true, -1, to
|
||||
}
|
||||
|
||||
// BreadthFirst traverses a directed or undirected graph in breadth
|
||||
// first order.
|
||||
//
|
||||
// Traversal starts at node start and visits the nodes reachable from
|
||||
// start. The function visit is called for each node visited. Nodes
|
||||
// not reachable from start are not visited.
|
||||
//
|
||||
// There are equivalent labeled and unlabeled versions of this method.
|
||||
//
|
||||
// See also alt.BreadthFirst, a variant with more options, and
|
||||
// alt.BreadthFirst2, a direction optimizing variant.
|
||||
func (g AdjacencyList) BreadthFirst(start NI, visit func(NI)) {
|
||||
v := bits.New(len(g))
|
||||
v.SetBit(int(start), 1)
|
||||
visit(start)
|
||||
var next []NI
|
||||
for frontier := []NI{start}; len(frontier) > 0; {
|
||||
for _, n := range frontier {
|
||||
for _, nb := range g[n] {
|
||||
if v.Bit(int(nb)) == 0 {
|
||||
v.SetBit(int(nb), 1)
|
||||
visit(nb)
|
||||
next = append(next, nb)
|
||||
}
|
||||
}
|
||||
}
|
||||
frontier, next = next, frontier[:0]
|
||||
}
|
||||
}
|
||||
|
||||
// Copy makes a deep copy of g.
|
||||
// Copy also computes the arc size ma, the number of arcs.
|
||||
//
|
||||
// There are equivalent labeled and unlabeled versions of this method.
|
||||
func (g AdjacencyList) Copy() (c AdjacencyList, ma int) {
|
||||
c = make(AdjacencyList, len(g))
|
||||
for n, to := range g {
|
||||
c[n] = append([]NI{}, to...)
|
||||
ma += len(to)
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
// DepthFirst traverses a directed or undirected graph in depth
|
||||
// first order.
|
||||
//
|
||||
// Traversal starts at node start and visits the nodes reachable from
|
||||
// start. The function visit is called for each node visited. Nodes
|
||||
// not reachable from start are not visited.
|
||||
//
|
||||
// There are equivalent labeled and unlabeled versions of this method.
|
||||
//
|
||||
// See also alt.DepthFirst, a variant with more options.
|
||||
func (g AdjacencyList) DepthFirst(start NI, visit func(NI)) {
|
||||
v := bits.New(len(g))
|
||||
var f func(NI)
|
||||
f = func(n NI) {
|
||||
visit(n)
|
||||
v.SetBit(int(n), 1)
|
||||
for _, to := range g[n] {
|
||||
if v.Bit(int(to)) == 0 {
|
||||
f(to)
|
||||
}
|
||||
}
|
||||
}
|
||||
f(start)
|
||||
}
|
||||
|
||||
// HasArc returns true if g has any arc from node `fr` to node `to`.
|
||||
//
|
||||
// Also returned is the index within the slice of arcs from node `fr`.
|
||||
// If no arc from `fr` to `to` is present, HasArc returns false, -1.
|
||||
//
|
||||
// There are equivalent labeled and unlabeled versions of this method.
|
||||
//
|
||||
// See also the method ParallelArcs, which finds all parallel arcs from
|
||||
// `fr` to `to`.
|
||||
func (g AdjacencyList) HasArc(fr, to NI) (bool, int) {
|
||||
for x, h := range g[fr] {
|
||||
if h == to {
|
||||
return true, x
|
||||
}
|
||||
}
|
||||
return false, -1
|
||||
}
|
||||
|
||||
// AnyLoop identifies if a graph contains a loop, an arc that leads from a
|
||||
// a node back to the same node.
|
||||
//
|
||||
// If g contains a loop, the method returns true and an example of a node
|
||||
// with a loop. If there are no loops in g, the method returns false, -1.
|
||||
//
|
||||
// There are equivalent labeled and unlabeled versions of this method.
|
||||
func (g AdjacencyList) AnyLoop() (bool, NI) {
|
||||
for fr, to := range g {
|
||||
for _, to := range to {
|
||||
if NI(fr) == to {
|
||||
return true, to
|
||||
}
|
||||
}
|
||||
}
|
||||
return false, -1
|
||||
}
|
||||
|
||||
// AddNode maps a node in a supergraph to a subgraph node.
|
||||
//
|
||||
// Argument p must be an NI in supergraph s.Super. AddNode panics if
|
||||
// p is not a valid node index of s.Super.
|
||||
//
|
||||
// AddNode is idempotent in that it does not add a new node to the subgraph if
|
||||
// a subgraph node already exists mapped to supergraph node p.
|
||||
//
|
||||
// The mapped subgraph NI is returned.
|
||||
func (s *Subgraph) AddNode(p NI) (b NI) {
|
||||
if int(p) < 0 || int(p) >= s.Super.Order() {
|
||||
panic(fmt.Sprint("AddNode: NI ", p, " not in supergraph"))
|
||||
}
|
||||
if b, ok := s.SubNI[p]; ok {
|
||||
return b
|
||||
}
|
||||
a := s.AdjacencyList
|
||||
b = NI(len(a))
|
||||
s.AdjacencyList = append(a, nil)
|
||||
s.SuperNI = append(s.SuperNI, p)
|
||||
s.SubNI[p] = b
|
||||
return
|
||||
}
|
||||
|
||||
// AddArc adds an arc to a subgraph.
|
||||
//
|
||||
// Arguments fr, to must be NIs in supergraph s.Super. As with AddNode,
|
||||
// AddArc panics if fr and to are not valid node indexes of s.Super.
|
||||
//
|
||||
// The arc specfied by fr, to must exist in s.Super. Further, the number of
|
||||
// parallel arcs in the subgraph cannot exceed the number of corresponding
|
||||
// parallel arcs in the supergraph. That is, each arc already added to the
|
||||
// subgraph counts against the arcs available in the supergraph. If a matching
|
||||
// arc is not available, AddArc returns an error.
|
||||
//
|
||||
// If a matching arc is available, subgraph nodes are added as needed, the
|
||||
// subgraph arc is added, and the method returns nil.
|
||||
func (s *Subgraph) AddArc(fr NI, to NI) error {
|
||||
// verify supergraph NIs first, but without adding subgraph nodes just yet.
|
||||
if int(fr) < 0 || int(fr) >= s.Super.Order() {
|
||||
panic(fmt.Sprint("AddArc: NI ", fr, " not in supergraph"))
|
||||
}
|
||||
if int(to) < 0 || int(to) >= s.Super.Order() {
|
||||
panic(fmt.Sprint("AddArc: NI ", to, " not in supergraph"))
|
||||
}
|
||||
// count existing matching arcs in subgraph
|
||||
n := 0
|
||||
a := s.AdjacencyList
|
||||
if bf, ok := s.SubNI[fr]; ok {
|
||||
if bt, ok := s.SubNI[to]; ok {
|
||||
// both NIs already exist in subgraph, need to count arcs
|
||||
bTo := to
|
||||
bTo = bt
|
||||
for _, t := range a[bf] {
|
||||
if t == bTo {
|
||||
n++
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
// verify matching arcs are available in supergraph
|
||||
for _, t := range (*s.Super)[fr] {
|
||||
if t == to {
|
||||
if n > 0 {
|
||||
n-- // match existing arc
|
||||
continue
|
||||
}
|
||||
// no more existing arcs need to be matched. nodes can finally
|
||||
// be added as needed and then the arc can be added.
|
||||
bf := s.AddNode(fr)
|
||||
to = s.AddNode(to)
|
||||
s.AdjacencyList[bf] = append(s.AdjacencyList[bf], to)
|
||||
return nil // success
|
||||
}
|
||||
}
|
||||
return errors.New("arc not available in supergraph")
|
||||
}
|
||||
|
||||
func (super AdjacencyList) induceArcs(sub map[NI]NI, sup []NI) AdjacencyList {
|
||||
s := make(AdjacencyList, len(sup))
|
||||
for b, p := range sup {
|
||||
var a []NI
|
||||
for _, to := range super[p] {
|
||||
if bt, ok := sub[to]; ok {
|
||||
to = bt
|
||||
a = append(a, to)
|
||||
}
|
||||
}
|
||||
s[b] = a
|
||||
}
|
||||
return s
|
||||
}
|
||||
|
||||
// InduceList constructs a node-induced subgraph.
|
||||
//
|
||||
// The subgraph is induced on receiver graph g. Argument l must be a list of
|
||||
// NIs in receiver graph g. Receiver g becomes the supergraph of the induced
|
||||
// subgraph.
|
||||
//
|
||||
// Duplicate NIs are allowed in list l. The duplicates are effectively removed
|
||||
// and only a single corresponding node is created in the subgraph. Subgraph
|
||||
// NIs are mapped in the order of list l, execpt for ignoring duplicates.
|
||||
// NIs in l that are not in g will panic.
|
||||
//
|
||||
// Returned is the constructed Subgraph object containing the induced subgraph
|
||||
// and the mappings to the supergraph.
|
||||
func (g *AdjacencyList) InduceList(l []NI) *Subgraph {
|
||||
sub, sup := mapList(l)
|
||||
return &Subgraph{
|
||||
Super: g,
|
||||
SubNI: sub,
|
||||
SuperNI: sup,
|
||||
|
||||
AdjacencyList: g.induceArcs(sub, sup)}
|
||||
}
|
||||
|
||||
// InduceBits constructs a node-induced subgraph.
|
||||
//
|
||||
// The subgraph is induced on receiver graph g. Argument t must be a bitmap
|
||||
// representing NIs in receiver graph g. Receiver g becomes the supergraph
|
||||
// of the induced subgraph. NIs in t that are not in g will panic.
|
||||
//
|
||||
// Returned is the constructed Subgraph object containing the induced subgraph
|
||||
// and the mappings to the supergraph.
|
||||
func (g *AdjacencyList) InduceBits(t bits.Bits) *Subgraph {
|
||||
sub, sup := mapBits(t)
|
||||
return &Subgraph{
|
||||
Super: g,
|
||||
SubNI: sub,
|
||||
SuperNI: sup,
|
||||
|
||||
AdjacencyList: g.induceArcs(sub, sup)}
|
||||
}
|
||||
|
||||
// IsSimple checks for loops and parallel arcs.
|
||||
//
|
||||
// A graph is "simple" if it has no loops or parallel arcs.
|
||||
//
|
||||
// IsSimple returns true, -1 for simple graphs. If a loop or parallel arc is
|
||||
// found, simple returns false and a node that represents a counterexample
|
||||
// to the graph being simple.
|
||||
//
|
||||
// See also separate methods AnyLoop and AnyParallel.
|
||||
//
|
||||
// There are equivalent labeled and unlabeled versions of this method.
|
||||
func (g AdjacencyList) IsSimple() (ok bool, n NI) {
|
||||
if lp, n := g.AnyLoop(); lp {
|
||||
return false, n
|
||||
}
|
||||
if pa, n, _ := g.AnyParallel(); pa {
|
||||
return false, n
|
||||
}
|
||||
return true, -1
|
||||
}
|
||||
|
||||
// IsolatedNodes returns a bitmap of isolated nodes in receiver graph g.
|
||||
//
|
||||
// An isolated node is one with no arcs going to or from it.
|
||||
//
|
||||
// There are equivalent labeled and unlabeled versions of this method.
|
||||
func (g AdjacencyList) IsolatedNodes() (i bits.Bits) {
|
||||
i = bits.New(len(g))
|
||||
i.SetAll()
|
||||
for fr, to := range g {
|
||||
if len(to) > 0 {
|
||||
i.SetBit(fr, 0)
|
||||
for _, to := range to {
|
||||
i.SetBit(int(to), 0)
|
||||
}
|
||||
}
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
// Order is the number of nodes in receiver g.
|
||||
//
|
||||
// It is simply a wrapper method for the Go builtin len().
|
||||
//
|
||||
// There are equivalent labeled and unlabeled versions of this method.
|
||||
func (g AdjacencyList) Order() int {
|
||||
// Why a wrapper for len()? Mostly for Directed and Undirected.
|
||||
// u.Order() is a little nicer than len(u.LabeledAdjacencyList).
|
||||
return len(g)
|
||||
}
|
||||
|
||||
// ParallelArcs identifies all arcs from node `fr` to node `to`.
|
||||
//
|
||||
// The returned slice contains an element for each arc from node `fr` to node `to`.
|
||||
// The element value is the index within the slice of arcs from node `fr`.
|
||||
//
|
||||
// There are equivalent labeled and unlabeled versions of this method.
|
||||
//
|
||||
// See also the method HasArc, which stops after finding a single arc.
|
||||
func (g AdjacencyList) ParallelArcs(fr, to NI) (p []int) {
|
||||
for x, h := range g[fr] {
|
||||
if h == to {
|
||||
p = append(p, x)
|
||||
}
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
// Permute permutes the node labeling of receiver g.
|
||||
//
|
||||
// Argument p must be a permutation of the node numbers of the graph,
|
||||
// 0 through len(g)-1. A permutation returned by rand.Perm(len(g)) for
|
||||
// example is acceptable.
|
||||
//
|
||||
// The graph is permuted in place. The graph keeps the same underlying
|
||||
// memory but values of the graph representation are permuted to produce
|
||||
// an isomorphic graph. The node previously labeled 0 becomes p[0] and so on.
|
||||
// See example (or the code) for clarification.
|
||||
//
|
||||
// There are equivalent labeled and unlabeled versions of this method.
|
||||
func (g AdjacencyList) Permute(p []int) {
|
||||
old := append(AdjacencyList{}, g...) // shallow copy
|
||||
for fr, arcs := range old {
|
||||
for i, to := range arcs {
|
||||
arcs[i] = NI(p[to])
|
||||
}
|
||||
g[p[fr]] = arcs
|
||||
}
|
||||
}
|
||||
|
||||
// ShuffleArcLists shuffles the arc lists of each node of receiver g.
|
||||
//
|
||||
// For example a node with arcs leading to nodes 3 and 7 might have an
|
||||
// arc list of either [3 7] or [7 3] after calling this method. The
|
||||
// connectivity of the graph is not changed. The resulting graph stays
|
||||
// equivalent but a traversal will encounter arcs in a different
|
||||
// order.
|
||||
//
|
||||
// If Rand r is nil, the rand package default shared source is used.
|
||||
//
|
||||
// There are equivalent labeled and unlabeled versions of this method.
|
||||
func (g AdjacencyList) ShuffleArcLists(r *rand.Rand) {
|
||||
ri := rand.Intn
|
||||
if r != nil {
|
||||
ri = r.Intn
|
||||
}
|
||||
// Knuth-Fisher-Yates
|
||||
for _, to := range g {
|
||||
for i := len(to); i > 1; {
|
||||
j := ri(i)
|
||||
i--
|
||||
to[i], to[j] = to[j], to[i]
|
||||
}
|
||||
}
|
||||
}
|
417
vendor/github.com/soniakeys/graph/adj_cg.go
generated
vendored
Normal file
417
vendor/github.com/soniakeys/graph/adj_cg.go
generated
vendored
Normal file
|
@ -0,0 +1,417 @@
|
|||
// Copyright 2014 Sonia Keys
|
||||
// License MIT: http://opensource.org/licenses/MIT
|
||||
|
||||
package graph
|
||||
|
||||
// adj_RO.go is code generated from adj_cg.go by directives in graph.go.
|
||||
// Editing adj_cg.go is okay.
|
||||
// DO NOT EDIT adj_RO.go. The RO is for Read Only.
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"math/rand"
|
||||
|
||||
"github.com/soniakeys/bits"
|
||||
)
|
||||
|
||||
// ArcDensity returns density for an simple directed graph.
|
||||
//
|
||||
// See also ArcDensity function.
|
||||
//
|
||||
// There are equivalent labeled and unlabeled versions of this method.
|
||||
func (g LabeledAdjacencyList) ArcDensity() float64 {
|
||||
return ArcDensity(len(g), g.ArcSize())
|
||||
}
|
||||
|
||||
// ArcSize returns the number of arcs in g.
|
||||
//
|
||||
// Note that for an undirected graph without loops, the number of undirected
|
||||
// edges -- the traditional meaning of graph size -- will be ArcSize()/2.
|
||||
// On the other hand, if g is an undirected graph that has or may have loops,
|
||||
// g.ArcSize()/2 is not a meaningful quantity.
|
||||
//
|
||||
// There are equivalent labeled and unlabeled versions of this method.
|
||||
func (g LabeledAdjacencyList) ArcSize() int {
|
||||
m := 0
|
||||
for _, to := range g {
|
||||
m += len(to)
|
||||
}
|
||||
return m
|
||||
}
|
||||
|
||||
// BoundsOk validates that all arcs in g stay within the slice bounds of g.
|
||||
//
|
||||
// BoundsOk returns true when no arcs point outside the bounds of g.
|
||||
// Otherwise it returns false and an example arc that points outside of g.
|
||||
//
|
||||
// Most methods of this package assume the BoundsOk condition and may
|
||||
// panic when they encounter an arc pointing outside of the graph. This
|
||||
// function can be used to validate a graph when the BoundsOk condition
|
||||
// is unknown.
|
||||
//
|
||||
// There are equivalent labeled and unlabeled versions of this method.
|
||||
func (g LabeledAdjacencyList) BoundsOk() (ok bool, fr NI, to Half) {
|
||||
for fr, to := range g {
|
||||
for _, to := range to {
|
||||
if to.To < 0 || to.To >= NI(len(g)) {
|
||||
return false, NI(fr), to
|
||||
}
|
||||
}
|
||||
}
|
||||
return true, -1, to
|
||||
}
|
||||
|
||||
// BreadthFirst traverses a directed or undirected graph in breadth
|
||||
// first order.
|
||||
//
|
||||
// Traversal starts at node start and visits the nodes reachable from
|
||||
// start. The function visit is called for each node visited. Nodes
|
||||
// not reachable from start are not visited.
|
||||
//
|
||||
// There are equivalent labeled and unlabeled versions of this method.
|
||||
//
|
||||
// See also alt.BreadthFirst, a variant with more options, and
|
||||
// alt.BreadthFirst2, a direction optimizing variant.
|
||||
func (g LabeledAdjacencyList) BreadthFirst(start NI, visit func(NI)) {
|
||||
v := bits.New(len(g))
|
||||
v.SetBit(int(start), 1)
|
||||
visit(start)
|
||||
var next []NI
|
||||
for frontier := []NI{start}; len(frontier) > 0; {
|
||||
for _, n := range frontier {
|
||||
for _, nb := range g[n] {
|
||||
if v.Bit(int(nb.To)) == 0 {
|
||||
v.SetBit(int(nb.To), 1)
|
||||
visit(nb.To)
|
||||
next = append(next, nb.To)
|
||||
}
|
||||
}
|
||||
}
|
||||
frontier, next = next, frontier[:0]
|
||||
}
|
||||
}
|
||||
|
||||
// Copy makes a deep copy of g.
|
||||
// Copy also computes the arc size ma, the number of arcs.
|
||||
//
|
||||
// There are equivalent labeled and unlabeled versions of this method.
|
||||
func (g LabeledAdjacencyList) Copy() (c LabeledAdjacencyList, ma int) {
|
||||
c = make(LabeledAdjacencyList, len(g))
|
||||
for n, to := range g {
|
||||
c[n] = append([]Half{}, to...)
|
||||
ma += len(to)
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
// DepthFirst traverses a directed or undirected graph in depth
|
||||
// first order.
|
||||
//
|
||||
// Traversal starts at node start and visits the nodes reachable from
|
||||
// start. The function visit is called for each node visited. Nodes
|
||||
// not reachable from start are not visited.
|
||||
//
|
||||
// There are equivalent labeled and unlabeled versions of this method.
|
||||
//
|
||||
// See also alt.DepthFirst, a variant with more options.
|
||||
func (g LabeledAdjacencyList) DepthFirst(start NI, visit func(NI)) {
|
||||
v := bits.New(len(g))
|
||||
var f func(NI)
|
||||
f = func(n NI) {
|
||||
visit(n)
|
||||
v.SetBit(int(n), 1)
|
||||
for _, to := range g[n] {
|
||||
if v.Bit(int(to.To)) == 0 {
|
||||
f(to.To)
|
||||
}
|
||||
}
|
||||
}
|
||||
f(start)
|
||||
}
|
||||
|
||||
// HasArc returns true if g has any arc from node `fr` to node `to`.
|
||||
//
|
||||
// Also returned is the index within the slice of arcs from node `fr`.
|
||||
// If no arc from `fr` to `to` is present, HasArc returns false, -1.
|
||||
//
|
||||
// There are equivalent labeled and unlabeled versions of this method.
|
||||
//
|
||||
// See also the method ParallelArcs, which finds all parallel arcs from
|
||||
// `fr` to `to`.
|
||||
func (g LabeledAdjacencyList) HasArc(fr, to NI) (bool, int) {
|
||||
for x, h := range g[fr] {
|
||||
if h.To == to {
|
||||
return true, x
|
||||
}
|
||||
}
|
||||
return false, -1
|
||||
}
|
||||
|
||||
// AnyLoop identifies if a graph contains a loop, an arc that leads from a
|
||||
// a node back to the same node.
|
||||
//
|
||||
// If g contains a loop, the method returns true and an example of a node
|
||||
// with a loop. If there are no loops in g, the method returns false, -1.
|
||||
//
|
||||
// There are equivalent labeled and unlabeled versions of this method.
|
||||
func (g LabeledAdjacencyList) AnyLoop() (bool, NI) {
|
||||
for fr, to := range g {
|
||||
for _, to := range to {
|
||||
if NI(fr) == to.To {
|
||||
return true, to.To
|
||||
}
|
||||
}
|
||||
}
|
||||
return false, -1
|
||||
}
|
||||
|
||||
// AddNode maps a node in a supergraph to a subgraph node.
|
||||
//
|
||||
// Argument p must be an NI in supergraph s.Super. AddNode panics if
|
||||
// p is not a valid node index of s.Super.
|
||||
//
|
||||
// AddNode is idempotent in that it does not add a new node to the subgraph if
|
||||
// a subgraph node already exists mapped to supergraph node p.
|
||||
//
|
||||
// The mapped subgraph NI is returned.
|
||||
func (s *LabeledSubgraph) AddNode(p NI) (b NI) {
|
||||
if int(p) < 0 || int(p) >= s.Super.Order() {
|
||||
panic(fmt.Sprint("AddNode: NI ", p, " not in supergraph"))
|
||||
}
|
||||
if b, ok := s.SubNI[p]; ok {
|
||||
return b
|
||||
}
|
||||
a := s.LabeledAdjacencyList
|
||||
b = NI(len(a))
|
||||
s.LabeledAdjacencyList = append(a, nil)
|
||||
s.SuperNI = append(s.SuperNI, p)
|
||||
s.SubNI[p] = b
|
||||
return
|
||||
}
|
||||
|
||||
// AddArc adds an arc to a subgraph.
|
||||
//
|
||||
// Arguments fr, to must be NIs in supergraph s.Super. As with AddNode,
|
||||
// AddArc panics if fr and to are not valid node indexes of s.Super.
|
||||
//
|
||||
// The arc specfied by fr, to must exist in s.Super. Further, the number of
|
||||
// parallel arcs in the subgraph cannot exceed the number of corresponding
|
||||
// parallel arcs in the supergraph. That is, each arc already added to the
|
||||
// subgraph counts against the arcs available in the supergraph. If a matching
|
||||
// arc is not available, AddArc returns an error.
|
||||
//
|
||||
// If a matching arc is available, subgraph nodes are added as needed, the
|
||||
// subgraph arc is added, and the method returns nil.
|
||||
func (s *LabeledSubgraph) AddArc(fr NI, to Half) error {
|
||||
// verify supergraph NIs first, but without adding subgraph nodes just yet.
|
||||
if int(fr) < 0 || int(fr) >= s.Super.Order() {
|
||||
panic(fmt.Sprint("AddArc: NI ", fr, " not in supergraph"))
|
||||
}
|
||||
if int(to.To) < 0 || int(to.To) >= s.Super.Order() {
|
||||
panic(fmt.Sprint("AddArc: NI ", to.To, " not in supergraph"))
|
||||
}
|
||||
// count existing matching arcs in subgraph
|
||||
n := 0
|
||||
a := s.LabeledAdjacencyList
|
||||
if bf, ok := s.SubNI[fr]; ok {
|
||||
if bt, ok := s.SubNI[to.To]; ok {
|
||||
// both NIs already exist in subgraph, need to count arcs
|
||||
bTo := to
|
||||
bTo.To = bt
|
||||
for _, t := range a[bf] {
|
||||
if t == bTo {
|
||||
n++
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
// verify matching arcs are available in supergraph
|
||||
for _, t := range (*s.Super)[fr] {
|
||||
if t == to {
|
||||
if n > 0 {
|
||||
n-- // match existing arc
|
||||
continue
|
||||
}
|
||||
// no more existing arcs need to be matched. nodes can finally
|
||||
// be added as needed and then the arc can be added.
|
||||
bf := s.AddNode(fr)
|
||||
to.To = s.AddNode(to.To)
|
||||
s.LabeledAdjacencyList[bf] = append(s.LabeledAdjacencyList[bf], to)
|
||||
return nil // success
|
||||
}
|
||||
}
|
||||
return errors.New("arc not available in supergraph")
|
||||
}
|
||||
|
||||
func (super LabeledAdjacencyList) induceArcs(sub map[NI]NI, sup []NI) LabeledAdjacencyList {
|
||||
s := make(LabeledAdjacencyList, len(sup))
|
||||
for b, p := range sup {
|
||||
var a []Half
|
||||
for _, to := range super[p] {
|
||||
if bt, ok := sub[to.To]; ok {
|
||||
to.To = bt
|
||||
a = append(a, to)
|
||||
}
|
||||
}
|
||||
s[b] = a
|
||||
}
|
||||
return s
|
||||
}
|
||||
|
||||
// InduceList constructs a node-induced subgraph.
|
||||
//
|
||||
// The subgraph is induced on receiver graph g. Argument l must be a list of
|
||||
// NIs in receiver graph g. Receiver g becomes the supergraph of the induced
|
||||
// subgraph.
|
||||
//
|
||||
// Duplicate NIs are allowed in list l. The duplicates are effectively removed
|
||||
// and only a single corresponding node is created in the subgraph. Subgraph
|
||||
// NIs are mapped in the order of list l, execpt for ignoring duplicates.
|
||||
// NIs in l that are not in g will panic.
|
||||
//
|
||||
// Returned is the constructed Subgraph object containing the induced subgraph
|
||||
// and the mappings to the supergraph.
|
||||
func (g *LabeledAdjacencyList) InduceList(l []NI) *LabeledSubgraph {
|
||||
sub, sup := mapList(l)
|
||||
return &LabeledSubgraph{
|
||||
Super: g,
|
||||
SubNI: sub,
|
||||
SuperNI: sup,
|
||||
|
||||
LabeledAdjacencyList: g.induceArcs(sub, sup)}
|
||||
}
|
||||
|
||||
// InduceBits constructs a node-induced subgraph.
|
||||
//
|
||||
// The subgraph is induced on receiver graph g. Argument t must be a bitmap
|
||||
// representing NIs in receiver graph g. Receiver g becomes the supergraph
|
||||
// of the induced subgraph. NIs in t that are not in g will panic.
|
||||
//
|
||||
// Returned is the constructed Subgraph object containing the induced subgraph
|
||||
// and the mappings to the supergraph.
|
||||
func (g *LabeledAdjacencyList) InduceBits(t bits.Bits) *LabeledSubgraph {
|
||||
sub, sup := mapBits(t)
|
||||
return &LabeledSubgraph{
|
||||
Super: g,
|
||||
SubNI: sub,
|
||||
SuperNI: sup,
|
||||
|
||||
LabeledAdjacencyList: g.induceArcs(sub, sup)}
|
||||
}
|
||||
|
||||
// IsSimple checks for loops and parallel arcs.
|
||||
//
|
||||
// A graph is "simple" if it has no loops or parallel arcs.
|
||||
//
|
||||
// IsSimple returns true, -1 for simple graphs. If a loop or parallel arc is
|
||||
// found, simple returns false and a node that represents a counterexample
|
||||
// to the graph being simple.
|
||||
//
|
||||
// See also separate methods AnyLoop and AnyParallel.
|
||||
//
|
||||
// There are equivalent labeled and unlabeled versions of this method.
|
||||
func (g LabeledAdjacencyList) IsSimple() (ok bool, n NI) {
|
||||
if lp, n := g.AnyLoop(); lp {
|
||||
return false, n
|
||||
}
|
||||
if pa, n, _ := g.AnyParallel(); pa {
|
||||
return false, n
|
||||
}
|
||||
return true, -1
|
||||
}
|
||||
|
||||
// IsolatedNodes returns a bitmap of isolated nodes in receiver graph g.
|
||||
//
|
||||
// An isolated node is one with no arcs going to or from it.
|
||||
//
|
||||
// There are equivalent labeled and unlabeled versions of this method.
|
||||
func (g LabeledAdjacencyList) IsolatedNodes() (i bits.Bits) {
|
||||
i = bits.New(len(g))
|
||||
i.SetAll()
|
||||
for fr, to := range g {
|
||||
if len(to) > 0 {
|
||||
i.SetBit(fr, 0)
|
||||
for _, to := range to {
|
||||
i.SetBit(int(to.To), 0)
|
||||
}
|
||||
}
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
// Order is the number of nodes in receiver g.
|
||||
//
|
||||
// It is simply a wrapper method for the Go builtin len().
|
||||
//
|
||||
// There are equivalent labeled and unlabeled versions of this method.
|
||||
func (g LabeledAdjacencyList) Order() int {
|
||||
// Why a wrapper for len()? Mostly for Directed and Undirected.
|
||||
// u.Order() is a little nicer than len(u.LabeledAdjacencyList).
|
||||
return len(g)
|
||||
}
|
||||
|
||||
// ParallelArcs identifies all arcs from node `fr` to node `to`.
|
||||
//
|
||||
// The returned slice contains an element for each arc from node `fr` to node `to`.
|
||||
// The element value is the index within the slice of arcs from node `fr`.
|
||||
//
|
||||
// There are equivalent labeled and unlabeled versions of this method.
|
||||
//
|
||||
// See also the method HasArc, which stops after finding a single arc.
|
||||
func (g LabeledAdjacencyList) ParallelArcs(fr, to NI) (p []int) {
|
||||
for x, h := range g[fr] {
|
||||
if h.To == to {
|
||||
p = append(p, x)
|
||||
}
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
// Permute permutes the node labeling of receiver g.
|
||||
//
|
||||
// Argument p must be a permutation of the node numbers of the graph,
|
||||
// 0 through len(g)-1. A permutation returned by rand.Perm(len(g)) for
|
||||
// example is acceptable.
|
||||
//
|
||||
// The graph is permuted in place. The graph keeps the same underlying
|
||||
// memory but values of the graph representation are permuted to produce
|
||||
// an isomorphic graph. The node previously labeled 0 becomes p[0] and so on.
|
||||
// See example (or the code) for clarification.
|
||||
//
|
||||
// There are equivalent labeled and unlabeled versions of this method.
|
||||
func (g LabeledAdjacencyList) Permute(p []int) {
|
||||
old := append(LabeledAdjacencyList{}, g...) // shallow copy
|
||||
for fr, arcs := range old {
|
||||
for i, to := range arcs {
|
||||
arcs[i].To = NI(p[to.To])
|
||||
}
|
||||
g[p[fr]] = arcs
|
||||
}
|
||||
}
|
||||
|
||||
// ShuffleArcLists shuffles the arc lists of each node of receiver g.
|
||||
//
|
||||
// For example a node with arcs leading to nodes 3 and 7 might have an
|
||||
// arc list of either [3 7] or [7 3] after calling this method. The
|
||||
// connectivity of the graph is not changed. The resulting graph stays
|
||||
// equivalent but a traversal will encounter arcs in a different
|
||||
// order.
|
||||
//
|
||||
// If Rand r is nil, the rand package default shared source is used.
|
||||
//
|
||||
// There are equivalent labeled and unlabeled versions of this method.
|
||||
func (g LabeledAdjacencyList) ShuffleArcLists(r *rand.Rand) {
|
||||
ri := rand.Intn
|
||||
if r != nil {
|
||||
ri = r.Intn
|
||||
}
|
||||
// Knuth-Fisher-Yates
|
||||
for _, to := range g {
|
||||
for i := len(to); i > 1; {
|
||||
j := ri(i)
|
||||
i--
|
||||
to[i], to[j] = to[j], to[i]
|
||||
}
|
||||
}
|
||||
}
|
1059
vendor/github.com/soniakeys/graph/dir.go
generated
vendored
Normal file
1059
vendor/github.com/soniakeys/graph/dir.go
generated
vendored
Normal file
File diff suppressed because it is too large
Load diff
1091
vendor/github.com/soniakeys/graph/dir_RO.go
generated
vendored
Normal file
1091
vendor/github.com/soniakeys/graph/dir_RO.go
generated
vendored
Normal file
File diff suppressed because it is too large
Load diff
1091
vendor/github.com/soniakeys/graph/dir_cg.go
generated
vendored
Normal file
1091
vendor/github.com/soniakeys/graph/dir_cg.go
generated
vendored
Normal file
File diff suppressed because it is too large
Load diff
122
vendor/github.com/soniakeys/graph/doc.go
generated
vendored
Normal file
122
vendor/github.com/soniakeys/graph/doc.go
generated
vendored
Normal file
|
@ -0,0 +1,122 @@
|
|||
// Copyright 2014 Sonia Keys
|
||||
// License MIT: http://opensource.org/licenses/MIT
|
||||
|
||||
// Graph algorithms: Dijkstra, A*, Bellman Ford, Floyd Warshall;
|
||||
// Kruskal and Prim minimal spanning tree; topological sort and DAG longest
|
||||
// and shortest paths; Eulerian cycle and path; degeneracy and k-cores;
|
||||
// Bron Kerbosch clique finding; connected components; dominance; and others.
|
||||
//
|
||||
// This is a graph library of integer indexes. To use it with application
|
||||
// data, you associate data with integer indexes, perform searches or other
|
||||
// operations with the library, and then use the integer index results to refer
|
||||
// back to your application data.
|
||||
//
|
||||
// Thus it does not store application data, pointers to application data,
|
||||
// or require you to implement an interface on your application data.
|
||||
// The idea is to keep the library methods fast and lean.
|
||||
//
|
||||
// Representation overview
|
||||
//
|
||||
// The package defines a type for a node index (NI) which is just an integer
|
||||
// type. It defines types for a number of number graph representations using
|
||||
// NI. The fundamental graph type is AdjacencyList, which is the
|
||||
// common "list of lists" graph representation. It is a list as a slice
|
||||
// with one element for each node of the graph. Each element is a list
|
||||
// itself, a list of neighbor nodes, implemented as an NI slice. Methods
|
||||
// on an AdjacencyList generally work on any representable graph, including
|
||||
// directed or undirected graphs, simple graphs or multigraphs.
|
||||
//
|
||||
// The type Undirected embeds an AdjacencyList adding methods specific to
|
||||
// undirected graphs. Similarly the type Directed adds methods meaningful
|
||||
// for directed graphs.
|
||||
//
|
||||
// Similar to NI, the type LI is a "label index" which labels a
|
||||
// node-to-neighbor "arc" or edge. Just as an NI can index arbitrary node
|
||||
// data, an LI can index arbitrary arc or edge data. A number of algorithms
|
||||
// use a "weight" associated with an arc. This package does not represent
|
||||
// weighted arcs explicitly, but instead uses the LI as a more general
|
||||
// mechanism allowing not only weights but arbitrary data to be associated
|
||||
// with arcs. While AdjacencyList represents an arc with simply an NI,
|
||||
// the type LabeledAdjacencyList uses a type that pairs an NI with an LI.
|
||||
// This type is named Half, for half-arc. (A full arc would represent
|
||||
// both ends.) Types LabeledDirected and LabeledUndirected embed a
|
||||
// LabeledAdjacencyList.
|
||||
//
|
||||
// In contrast to Half, the type Edge represents both ends of an edge (but
|
||||
// no label.) The type LabeledEdge adds the label. The type WeightedEdgeList
|
||||
// bundles a list of LabeledEdges with a WeightFunc. (WeightedEdgeList has
|
||||
// few methods. It exists primarily to support the Kruskal algorithm.)
|
||||
//
|
||||
// FromList is a compact rooted tree (or forest) respresentation. Like
|
||||
// AdjacencyList and LabeledAdjacencyList, it is a list with one element for
|
||||
// each node of the graph. Each element contains only a single neighbor
|
||||
// however, its parent in the tree, the "from" node.
|
||||
//
|
||||
// Code generation
|
||||
//
|
||||
// A number of methods on AdjacencyList, Directed, and Undirected are
|
||||
// applicable to LabeledAdjacencyList, LabeledDirected, and LabeledUndirected
|
||||
// simply by ignoring the label. In these cases code generation provides
|
||||
// methods on both types from a single source implementation. These methods
|
||||
// are documented with the sentence "There are equivalent labeled and unlabeled
|
||||
// versions of this method."
|
||||
//
|
||||
// Terminology
|
||||
//
|
||||
// This package uses the term "node" rather than "vertex." It uses "arc"
|
||||
// to mean a directed edge, and uses "from" and "to" to refer to the ends
|
||||
// of an arc. It uses "start" and "end" to refer to endpoints of a search
|
||||
// or traversal.
|
||||
//
|
||||
// The usage of "to" and "from" is perhaps most strange. In common speech
|
||||
// they are prepositions, but throughout this package they are used as
|
||||
// adjectives, for example to refer to the "from node" of an arc or the
|
||||
// "to node". The type "FromList" is named to indicate it stores a list of
|
||||
// "from" values.
|
||||
//
|
||||
// A "half arc" refers to just one end of an arc, either the to or from end.
|
||||
//
|
||||
// Two arcs are "reciprocal" if they connect two distinct nodes n1 and n2,
|
||||
// one arc leading from n1 to n2 and the other arc leading from n2 to n1.
|
||||
// Undirected graphs are represented with reciprocal arcs.
|
||||
//
|
||||
// A node with an arc to itself represents a "loop." Duplicate arcs, where
|
||||
// a node has multiple arcs to another node, are termed "parallel arcs."
|
||||
// A graph with no loops or parallel arcs is "simple." A graph that allows
|
||||
// parallel arcs is a "multigraph"
|
||||
//
|
||||
// The "size" of a graph traditionally means the number of undirected edges.
|
||||
// This package uses "arc size" to mean the number of arcs in a graph. For an
|
||||
// undirected graph without loops, arc size is 2 * size.
|
||||
//
|
||||
// The "order" of a graph is the number of nodes. An "ordering" though means
|
||||
// an ordered list of nodes.
|
||||
//
|
||||
// A number of graph search algorithms use a concept of arc "weights."
|
||||
// The sum of arc weights along a path is a "distance." In contrast, the
|
||||
// number of nodes in a path, including start and end nodes, is the path's
|
||||
// "length." (Yes, mixing weights and lengths would be nonsense physically,
|
||||
// but the terms used here are just distinct terms for abstract values.
|
||||
// The actual meaning to an application is likely to be something else
|
||||
// entirely and is not relevant within this package.)
|
||||
//
|
||||
// Finally, this package documentation takes back the word "object" in some
|
||||
// places to refer to a Go value, especially a value of a type with methods.
|
||||
//
|
||||
// Shortest path searches
|
||||
//
|
||||
// This package implements a number of shortest path searches. Most work
|
||||
// with weighted graphs that are directed or undirected, and with graphs
|
||||
// that may have loops or parallel arcs. For weighted graphs, "shortest"
|
||||
// is defined as the path distance (sum of arc weights) with path length
|
||||
// (number of nodes) breaking ties. If multiple paths have the same minimum
|
||||
// distance with the same minimum length, search methods are free to return
|
||||
// any of them.
|
||||
//
|
||||
// Algorithm Description
|
||||
// Dijkstra Non-negative arc weights, single or all paths.
|
||||
// AStar Non-negative arc weights, heuristic guided, single path.
|
||||
// BellmanFord Negative arc weights allowed, no negative cycles, all paths.
|
||||
// DAGPath O(n) algorithm for DAGs, arc weights of any sign.
|
||||
// FloydWarshall all pairs distances, no negative cycles.
|
||||
package graph
|
498
vendor/github.com/soniakeys/graph/fromlist.go
generated
vendored
Normal file
498
vendor/github.com/soniakeys/graph/fromlist.go
generated
vendored
Normal file
|
@ -0,0 +1,498 @@
|
|||
// Copyright 2014 Sonia Keys
|
||||
// License MIT: http://opensource.org/licenses/MIT
|
||||
|
||||
package graph
|
||||
|
||||
import "github.com/soniakeys/bits"
|
||||
|
||||
// FromList represents a rooted tree (or forest) where each node is associated
|
||||
// with a half arc identifying an arc "from" another node.
|
||||
//
|
||||
// Other terms for this data structure include "parent list",
|
||||
// "predecessor list", "in-tree", "inverse arborescence", and
|
||||
// "spaghetti stack."
|
||||
//
|
||||
// The Paths member represents the tree structure. Leaves and MaxLen are
|
||||
// not always needed. Where Leaves is used it serves as a bitmap where
|
||||
// Leaves.Bit(n) == 1 for each leaf n of the tree. Where MaxLen is used it is
|
||||
// provided primarily as a convenience for functions that might want to
|
||||
// anticipate the maximum path length that would be encountered traversing
|
||||
// the tree.
|
||||
//
|
||||
// Various graph search methods use a FromList to returns search results.
|
||||
// For a start node of a search, From will be -1 and Len will be 1. For other
|
||||
// nodes reached by the search, From represents a half arc in a path back to
|
||||
// start and Len represents the number of nodes in the path. For nodes not
|
||||
// reached by the search, From will be -1 and Len will be 0.
|
||||
//
|
||||
// A single FromList can also represent a forest. In this case paths from
|
||||
// all leaves do not return to a single root node, but multiple root nodes.
|
||||
//
|
||||
// While a FromList generally encodes a tree or forest, it is technically
|
||||
// possible to encode a cyclic graph. A number of FromList methods require
|
||||
// the receiver to be acyclic. Graph methods documented to return a tree or
|
||||
// forest will never return a cyclic FromList. In other cases however,
|
||||
// where a FromList is not known to by cyclic, the Cyclic method can be
|
||||
// useful to validate the acyclic property.
|
||||
type FromList struct {
|
||||
Paths []PathEnd // tree representation
|
||||
Leaves bits.Bits // leaves of tree
|
||||
MaxLen int // length of longest path, max of all PathEnd.Len values
|
||||
}
|
||||
|
||||
// PathEnd associates a half arc and a path length.
|
||||
//
|
||||
// A PathEnd list is an element type of FromList.
|
||||
type PathEnd struct {
|
||||
From NI // a "from" half arc, the node the arc comes from
|
||||
Len int // number of nodes in path from start
|
||||
}
|
||||
|
||||
/* NewFromList could be confusing now with bits also needing allocation.
|
||||
maybe best to not have this function. Maybe a more useful new would be
|
||||
one that took a PathEnd slice and intitialized everything including roots
|
||||
and max len. Maybe its time for a separate []PathEnd type when that's
|
||||
all that's needed. (and reconsider the name PathEnd)
|
||||
*/
|
||||
|
||||
// NewFromList creates a FromList object of given order.
|
||||
//
|
||||
// The Paths member is allocated to the specified order n but other members
|
||||
// are left as zero values.
|
||||
func NewFromList(n int) FromList {
|
||||
return FromList{Paths: make([]PathEnd, n)}
|
||||
}
|
||||
|
||||
// BoundsOk validates the "from" values in the list.
|
||||
//
|
||||
// Negative values are allowed as they indicate root nodes.
|
||||
//
|
||||
// BoundsOk returns true when all from values are less than len(t).
|
||||
// Otherwise it returns false and a node with a from value >= len(t).
|
||||
func (f FromList) BoundsOk() (ok bool, n NI) {
|
||||
for n, e := range f.Paths {
|
||||
if int(e.From) >= len(f.Paths) {
|
||||
return false, NI(n)
|
||||
}
|
||||
}
|
||||
return true, -1
|
||||
}
|
||||
|
||||
// CommonStart returns the common start node of minimal paths to a and b.
|
||||
//
|
||||
// It returns -1 if a and b cannot be traced back to a common node.
|
||||
//
|
||||
// The method relies on populated PathEnd.Len members. Use RecalcLen if
|
||||
// the Len members are not known to be present and correct.
|
||||
func (f FromList) CommonStart(a, b NI) NI {
|
||||
p := f.Paths
|
||||
if p[a].Len < p[b].Len {
|
||||
a, b = b, a
|
||||
}
|
||||
for bl := p[b].Len; p[a].Len > bl; {
|
||||
a = p[a].From
|
||||
if a < 0 {
|
||||
return -1
|
||||
}
|
||||
}
|
||||
for a != b {
|
||||
a = p[a].From
|
||||
if a < 0 {
|
||||
return -1
|
||||
}
|
||||
b = p[b].From
|
||||
}
|
||||
return a
|
||||
}
|
||||
|
||||
// Cyclic determines if f contains a cycle, a non-empty path from a node
|
||||
// back to itself.
|
||||
//
|
||||
// Cyclic returns true if g contains at least one cycle. It also returns
|
||||
// an example of a node involved in a cycle.
|
||||
//
|
||||
// Cyclic returns (false, -1) in the normal case where f is acyclic.
|
||||
// Note that the bool is not an "ok" return. A cyclic FromList is usually
|
||||
// not okay.
|
||||
func (f FromList) Cyclic() (cyclic bool, n NI) {
|
||||
p := f.Paths
|
||||
vis := bits.New(len(p))
|
||||
for i := range p {
|
||||
path := bits.New(len(p))
|
||||
for n := i; vis.Bit(n) == 0; {
|
||||
vis.SetBit(n, 1)
|
||||
path.SetBit(n, 1)
|
||||
if n = int(p[n].From); n < 0 {
|
||||
break
|
||||
}
|
||||
if path.Bit(n) == 1 {
|
||||
return true, NI(n)
|
||||
}
|
||||
}
|
||||
}
|
||||
return false, -1
|
||||
}
|
||||
|
||||
// IsolatedNodeBits returns a bitmap of isolated nodes in receiver graph f.
|
||||
//
|
||||
// An isolated node is one with no arcs going to or from it.
|
||||
func (f FromList) IsolatedNodes() (iso bits.Bits) {
|
||||
p := f.Paths
|
||||
iso = bits.New(len(p))
|
||||
iso.SetAll()
|
||||
for n, e := range p {
|
||||
if e.From >= 0 {
|
||||
iso.SetBit(n, 0)
|
||||
iso.SetBit(int(e.From), 0)
|
||||
}
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
// PathTo decodes a FromList, recovering a single path.
|
||||
//
|
||||
// The path is returned as a list of nodes where the first element will be
|
||||
// a root node and the last element will be the specified end node.
|
||||
//
|
||||
// Only the Paths member of the receiver is used. Other members of the
|
||||
// FromList do not need to be valid, however the MaxLen member can be useful
|
||||
// for allocating argument p.
|
||||
//
|
||||
// Argument p can provide the result slice. If p has capacity for the result
|
||||
// it will be used, otherwise a new slice is created for the result.
|
||||
//
|
||||
// See also function PathTo.
|
||||
func (f FromList) PathTo(end NI, p []NI) []NI {
|
||||
return PathTo(f.Paths, end, p)
|
||||
}
|
||||
|
||||
// PathTo decodes a single path from a PathEnd list.
|
||||
//
|
||||
// A PathEnd list is the main data representation in a FromList. See FromList.
|
||||
//
|
||||
// PathTo returns a list of nodes where the first element will be
|
||||
// a root node and the last element will be the specified end node.
|
||||
//
|
||||
// Argument p can provide the result slice. If p has capacity for the result
|
||||
// it will be used, otherwise a new slice is created for the result.
|
||||
//
|
||||
// See also method FromList.PathTo.
|
||||
func PathTo(paths []PathEnd, end NI, p []NI) []NI {
|
||||
n := paths[end].Len
|
||||
if n == 0 {
|
||||
return p[:0]
|
||||
}
|
||||
if cap(p) >= n {
|
||||
p = p[:n]
|
||||
} else {
|
||||
p = make([]NI, n)
|
||||
}
|
||||
for {
|
||||
n--
|
||||
p[n] = end
|
||||
if n == 0 {
|
||||
return p
|
||||
}
|
||||
end = paths[end].From
|
||||
}
|
||||
}
|
||||
|
||||
// PathToLabeled decodes a FromList, recovering a single path.
|
||||
//
|
||||
// The start of the returned path will be a root node of the FromList.
|
||||
//
|
||||
// Only the Paths member of the receiver is used. Other members of the
|
||||
// FromList do not need to be valid, however the MaxLen member can be useful
|
||||
// for allocating argument p.
|
||||
//
|
||||
// Argument p can provide the result slice. If p has capacity for the result
|
||||
// it will be used, otherwise a new slice is created for the result.
|
||||
//
|
||||
// See also function PathTo.
|
||||
func (f FromList) PathToLabeled(end NI, labels []LI, p []Half) LabeledPath {
|
||||
n := f.Paths[end].Len - 1
|
||||
if n <= 0 {
|
||||
return LabeledPath{end, p[:0]}
|
||||
}
|
||||
if cap(p) >= n {
|
||||
p = p[:n]
|
||||
} else {
|
||||
p = make([]Half, n)
|
||||
}
|
||||
for {
|
||||
n--
|
||||
p[n] = Half{To: end, Label: labels[end]}
|
||||
end = f.Paths[end].From
|
||||
if n == 0 {
|
||||
return LabeledPath{end, p}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Preorder traverses a FromList in preorder.
|
||||
//
|
||||
// Nodes are visited in order such that for any node n with from node fr,
|
||||
// fr is visited before n. Where f represents a tree, the visit ordering
|
||||
// corresponds to a preordering, or depth first traversal of the tree.
|
||||
// Where f represents a forest, the preorderings of the trees can be
|
||||
// intermingled.
|
||||
//
|
||||
// Leaves must be set correctly first. Use RecalcLeaves if leaves are not
|
||||
// known to be set correctly. FromList f cannot be cyclic.
|
||||
//
|
||||
// Traversal continues while visitor function v returns true. It terminates
|
||||
// if v returns false. Preorder returns true if it completes without v
|
||||
// returning false. Preorder returns false if traversal is terminated by v
|
||||
// returning false.
|
||||
func (f FromList) Preorder(v func(NI) bool) bool {
|
||||
p := f.Paths
|
||||
done := bits.New(len(p))
|
||||
var df func(NI) bool
|
||||
df = func(n NI) bool {
|
||||
done.SetBit(int(n), 1)
|
||||
if fr := p[n].From; fr >= 0 && done.Bit(int(fr)) == 0 {
|
||||
df(fr)
|
||||
}
|
||||
return v(n)
|
||||
}
|
||||
for n := range f.Paths {
|
||||
p[n].Len = 0
|
||||
}
|
||||
return f.Leaves.IterateOnes(func(n int) bool {
|
||||
return df(NI(n))
|
||||
})
|
||||
}
|
||||
|
||||
// RecalcLeaves recomputes the Leaves member of f.
|
||||
func (f *FromList) RecalcLeaves() {
|
||||
p := f.Paths
|
||||
lv := &f.Leaves
|
||||
if lv.Num != len(p) {
|
||||
*lv = bits.New(len(p))
|
||||
}
|
||||
lv.SetAll()
|
||||
for n := range f.Paths {
|
||||
if fr := p[n].From; fr >= 0 {
|
||||
lv.SetBit(int(fr), 0)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// RecalcLen recomputes Len for each path end, and recomputes MaxLen.
|
||||
//
|
||||
// RecalcLen relies on the Leaves member being valid. If it is not known
|
||||
// to be valid, call RecalcLeaves before calling RecalcLen.
|
||||
//
|
||||
// RecalcLen will panic if the FromList is cyclic. Use the Cyclic method
|
||||
// if needed to verify that the FromList is acyclic.
|
||||
func (f *FromList) RecalcLen() {
|
||||
p := f.Paths
|
||||
var setLen func(NI) int
|
||||
setLen = func(n NI) int {
|
||||
switch {
|
||||
case p[n].Len > 0:
|
||||
return p[n].Len
|
||||
case p[n].From < 0:
|
||||
p[n].Len = 1
|
||||
return 1
|
||||
}
|
||||
l := 1 + setLen(p[n].From)
|
||||
p[n].Len = l
|
||||
return l
|
||||
}
|
||||
for n := range f.Paths {
|
||||
p[n].Len = 0
|
||||
}
|
||||
f.MaxLen = 0
|
||||
f.Leaves.IterateOnes(func(n int) bool {
|
||||
if l := setLen(NI(n)); l > f.MaxLen {
|
||||
f.MaxLen = l
|
||||
}
|
||||
return true
|
||||
})
|
||||
}
|
||||
|
||||
// ReRoot reorients the tree containing n to make n the root node.
|
||||
//
|
||||
// It keeps the tree connected by "reversing" the path from n to the old root.
|
||||
//
|
||||
// After ReRoot, the Leaves and Len members are invalid.
|
||||
// Call RecalcLeaves or RecalcLen as needed.
|
||||
func (f *FromList) ReRoot(n NI) {
|
||||
p := f.Paths
|
||||
fr := p[n].From
|
||||
if fr < 0 {
|
||||
return
|
||||
}
|
||||
p[n].From = -1
|
||||
for {
|
||||
ff := p[fr].From
|
||||
p[fr].From = n
|
||||
if ff < 0 {
|
||||
return
|
||||
}
|
||||
n = fr
|
||||
fr = ff
|
||||
}
|
||||
}
|
||||
|
||||
// Root finds the root of a node in a FromList.
|
||||
func (f FromList) Root(n NI) NI {
|
||||
for p := f.Paths; ; {
|
||||
fr := p[n].From
|
||||
if fr < 0 {
|
||||
return n
|
||||
}
|
||||
n = fr
|
||||
}
|
||||
}
|
||||
|
||||
// Transpose constructs the directed graph corresponding to FromList f
|
||||
// but with arcs in the opposite direction. That is, from roots toward leaves.
|
||||
//
|
||||
// If non-nil argrument roots is passed, Transpose populates it as roots of
|
||||
// the resulting forest and returns nRoots as a count of the roots.
|
||||
//
|
||||
// The method relies only on the From member of f.Paths. Other members of
|
||||
// the FromList are not used.
|
||||
func (f FromList) Transpose(roots *bits.Bits) (forest Directed, nRoots int) {
|
||||
p := f.Paths
|
||||
g := make(AdjacencyList, len(p))
|
||||
if roots != nil {
|
||||
nRoots = len(p)
|
||||
if roots.Num != nRoots {
|
||||
*roots = bits.New(nRoots)
|
||||
}
|
||||
roots.SetAll()
|
||||
}
|
||||
for i, e := range p {
|
||||
if e.From == -1 {
|
||||
continue
|
||||
}
|
||||
g[e.From] = append(g[e.From], NI(i))
|
||||
if roots != nil && roots.Bit(i) == 1 {
|
||||
roots.SetBit(i, 0)
|
||||
nRoots--
|
||||
}
|
||||
}
|
||||
return Directed{g}, nRoots
|
||||
}
|
||||
|
||||
// TransposeLabeled constructs the labeled directed graph corresponding
|
||||
// to FromList f but with arcs in the opposite direction. That is, from
|
||||
// roots toward leaves.
|
||||
//
|
||||
// The argument labels can be nil. In this case labels are generated matching
|
||||
// the path indexes. This corresponds to the "to", or child node.
|
||||
//
|
||||
// If labels is non-nil, it must be the same length as t.Paths and is used
|
||||
// to look up label numbers by the path index.
|
||||
//
|
||||
// If non-nil argrument roots is passed, Transpose populates it as roots of
|
||||
// the resulting forest and returns nRoots as a count of the roots.
|
||||
//
|
||||
// The method relies only on the From member of f.Paths. Other members of
|
||||
// the FromList are not used.
|
||||
func (f FromList) TransposeLabeled(labels []LI, roots *bits.Bits) (forest LabeledDirected, nRoots int) {
|
||||
p := f.Paths
|
||||
g := make(LabeledAdjacencyList, len(p))
|
||||
if roots != nil {
|
||||
nRoots = len(p)
|
||||
if roots.Num != nRoots {
|
||||
*roots = bits.New(nRoots)
|
||||
}
|
||||
roots.SetAll()
|
||||
}
|
||||
for i, p := range f.Paths {
|
||||
if p.From == -1 {
|
||||
continue
|
||||
}
|
||||
l := LI(i)
|
||||
if labels != nil {
|
||||
l = labels[i]
|
||||
}
|
||||
g[p.From] = append(g[p.From], Half{NI(i), l})
|
||||
if roots != nil && roots.Bit(i) == 1 {
|
||||
roots.SetBit(i, 0)
|
||||
nRoots--
|
||||
}
|
||||
}
|
||||
return LabeledDirected{g}, nRoots
|
||||
}
|
||||
|
||||
// Undirected constructs the undirected graph corresponding to FromList f.
|
||||
//
|
||||
// The resulting graph will be a tree or forest.
|
||||
//
|
||||
// If non-nil argrument roots is passed, Transpose populates it as roots of
|
||||
// the resulting forest and returns nRoots as a count of the roots.
|
||||
//
|
||||
// The method relies only on the From member of f.Paths. Other members of
|
||||
// the FromList are not used.
|
||||
func (f FromList) Undirected(roots *bits.Bits) (forest Undirected, nRoots int) {
|
||||
p := f.Paths
|
||||
g := make(AdjacencyList, len(p))
|
||||
if roots != nil {
|
||||
nRoots = len(p)
|
||||
if roots.Num != nRoots {
|
||||
*roots = bits.New(nRoots)
|
||||
}
|
||||
roots.SetAll()
|
||||
}
|
||||
for i, e := range p {
|
||||
if e.From == -1 {
|
||||
continue
|
||||
}
|
||||
g[i] = append(g[i], e.From)
|
||||
g[e.From] = append(g[e.From], NI(i))
|
||||
if roots != nil && roots.Bit(i) == 1 {
|
||||
roots.SetBit(i, 0)
|
||||
nRoots--
|
||||
}
|
||||
}
|
||||
return Undirected{g}, nRoots
|
||||
}
|
||||
|
||||
// LabeledUndirected constructs the labeled undirected graph corresponding
|
||||
// to FromList f.
|
||||
//
|
||||
// The resulting graph will be a tree or forest.
|
||||
//
|
||||
// The argument labels can be nil. In this case labels are generated matching
|
||||
// the path indexes. This corresponds to the "to", or child node.
|
||||
//
|
||||
// If labels is non-nil, it must be the same length as t.Paths and is used
|
||||
// to look up label numbers by the path index.
|
||||
//
|
||||
// If non-nil argrument roots is passed, LabeledUndirected populates it as
|
||||
// roots of the resulting forest and returns nRoots as a count of the roots.
|
||||
//
|
||||
// The method relies only on the From member of f.Paths. Other members of
|
||||
// the FromList are not used.
|
||||
func (f FromList) LabeledUndirected(labels []LI, roots *bits.Bits) (forest LabeledUndirected, nRoots int) {
|
||||
p := f.Paths
|
||||
g := make(LabeledAdjacencyList, len(p))
|
||||
if roots != nil {
|
||||
nRoots = len(p)
|
||||
if roots.Num != nRoots {
|
||||
*roots = bits.New(nRoots)
|
||||
}
|
||||
roots.SetAll()
|
||||
}
|
||||
for i, p := range f.Paths {
|
||||
if p.From == -1 {
|
||||
continue
|
||||
}
|
||||
l := LI(i)
|
||||
if labels != nil {
|
||||
l = labels[i]
|
||||
}
|
||||
g[i] = append(g[i], Half{p.From, l})
|
||||
g[p.From] = append(g[p.From], Half{NI(i), l})
|
||||
if roots != nil && roots.Bit(i) == 1 {
|
||||
roots.SetBit(i, 0)
|
||||
nRoots--
|
||||
}
|
||||
}
|
||||
return LabeledUndirected{g}, nRoots
|
||||
}
|
3
vendor/github.com/soniakeys/graph/go.mod
generated
vendored
Normal file
3
vendor/github.com/soniakeys/graph/go.mod
generated
vendored
Normal file
|
@ -0,0 +1,3 @@
|
|||
module "github.com/soniakeys/graph"
|
||||
|
||||
require "github.com/soniakeys/bits" v1.0.0
|
767
vendor/github.com/soniakeys/graph/graph.go
generated
vendored
Normal file
767
vendor/github.com/soniakeys/graph/graph.go
generated
vendored
Normal file
|
@ -0,0 +1,767 @@
|
|||
// Copyright 2014 Sonia Keys
|
||||
// License MIT: http://opensource.org/licenses/MIT
|
||||
|
||||
package graph
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"errors"
|
||||
"fmt"
|
||||
"math"
|
||||
"reflect"
|
||||
"text/template"
|
||||
|
||||
"github.com/soniakeys/bits"
|
||||
)
|
||||
|
||||
// graph.go contains type definitions for all graph types and components.
|
||||
// Also, go generate directives for source transformations.
|
||||
//
|
||||
// For readability, the types are defined in a dependency order:
|
||||
//
|
||||
// NI
|
||||
// AdjacencyList
|
||||
// Directed
|
||||
// Undirected
|
||||
// Bipartite
|
||||
// Subgraph
|
||||
// DirectedSubgraph
|
||||
// UndirectedSubgraph
|
||||
// LI
|
||||
// Half
|
||||
// fromHalf
|
||||
// LabeledAdjacencyList
|
||||
// LabeledDirected
|
||||
// LabeledUndirected
|
||||
// LabeledBipartite
|
||||
// LabeledSubgraph
|
||||
// LabeledDirectedSubgraph
|
||||
// LabeledUndirectedSubgraph
|
||||
// Edge
|
||||
// LabeledEdge
|
||||
// LabeledPath
|
||||
// WeightFunc
|
||||
// WeightedEdgeList
|
||||
// TraverseOption
|
||||
|
||||
//go:generate cp adj_cg.go adj_RO.go
|
||||
//go:generate gofmt -r "LabeledAdjacencyList -> AdjacencyList" -w adj_RO.go
|
||||
//go:generate gofmt -r "n.To -> n" -w adj_RO.go
|
||||
//go:generate gofmt -r "Half -> NI" -w adj_RO.go
|
||||
//go:generate gofmt -r "LabeledSubgraph -> Subgraph" -w adj_RO.go
|
||||
|
||||
//go:generate cp dir_cg.go dir_RO.go
|
||||
//go:generate gofmt -r "LabeledDirected -> Directed" -w dir_RO.go
|
||||
//go:generate gofmt -r "LabeledDirectedSubgraph -> DirectedSubgraph" -w dir_RO.go
|
||||
//go:generate gofmt -r "LabeledAdjacencyList -> AdjacencyList" -w dir_RO.go
|
||||
//go:generate gofmt -r "labEulerian -> eulerian" -w dir_RO.go
|
||||
//go:generate gofmt -r "newLabEulerian -> newEulerian" -w dir_RO.go
|
||||
//go:generate gofmt -r "Half{n, -1} -> n" -w dir_RO.go
|
||||
//go:generate gofmt -r "n.To -> n" -w dir_RO.go
|
||||
//go:generate gofmt -r "Half -> NI" -w dir_RO.go
|
||||
|
||||
//go:generate cp undir_cg.go undir_RO.go
|
||||
//go:generate gofmt -r "LabeledUndirected -> Undirected" -w undir_RO.go
|
||||
//go:generate gofmt -r "LabeledBipartite -> Bipartite" -w undir_RO.go
|
||||
//go:generate gofmt -r "LabeledUndirectedSubgraph -> UndirectedSubgraph" -w undir_RO.go
|
||||
//go:generate gofmt -r "LabeledAdjacencyList -> AdjacencyList" -w undir_RO.go
|
||||
//go:generate gofmt -r "newLabEulerian -> newEulerian" -w undir_RO.go
|
||||
//go:generate gofmt -r "Half{n, -1} -> n" -w undir_RO.go
|
||||
//go:generate gofmt -r "n.To -> n" -w undir_RO.go
|
||||
//go:generate gofmt -r "Half -> NI" -w undir_RO.go
|
||||
|
||||
// An AdjacencyList represents a graph as a list of neighbors for each node.
|
||||
// The "node ID" of a node is simply it's slice index in the AdjacencyList.
|
||||
// For an AdjacencyList g, g[n] represents arcs going from node n to nodes
|
||||
// g[n].
|
||||
//
|
||||
// Adjacency lists are inherently directed but can be used to represent
|
||||
// directed or undirected graphs. See types Directed and Undirected.
|
||||
type AdjacencyList [][]NI
|
||||
|
||||
// Directed represents a directed graph.
|
||||
//
|
||||
// Directed methods generally rely on the graph being directed, specifically
|
||||
// that arcs do not have reciprocals.
|
||||
type Directed struct {
|
||||
AdjacencyList // embedded to include AdjacencyList methods
|
||||
}
|
||||
|
||||
// Undirected represents an undirected graph.
|
||||
//
|
||||
// In an undirected graph, for each arc between distinct nodes there is also
|
||||
// a reciprocal arc, an arc in the opposite direction. Loops do not have
|
||||
// reciprocals.
|
||||
//
|
||||
// Undirected methods generally rely on the graph being undirected,
|
||||
// specifically that every arc between distinct nodes has a reciprocal.
|
||||
type Undirected struct {
|
||||
AdjacencyList // embedded to include AdjacencyList methods
|
||||
}
|
||||
|
||||
// Bipartite represents a bipartite graph.
|
||||
//
|
||||
// In a bipartite graph, nodes are partitioned into two sets, or
|
||||
// "colors," such that every edge in the graph goes from one set to the
|
||||
// other.
|
||||
//
|
||||
// Member Color represents the partition with a bitmap of length the same
|
||||
// as the number of nodes in the graph. For convenience N0 stores the number
|
||||
// of zero bits in Color.
|
||||
//
|
||||
// To construct a Bipartite object, if you can easily or efficiently use
|
||||
// available information to construct the Color member, then you should do
|
||||
// this and construct a Bipartite object with a Go struct literal.
|
||||
//
|
||||
// If partition information is not readily available, see the constructor
|
||||
// Undirected.Bipartite.
|
||||
//
|
||||
// Alternatively, in some cases where the graph may have multiple connected
|
||||
// components, the lower level Undirected.BipartiteComponent can be used to
|
||||
// control color assignment by component.
|
||||
type Bipartite struct {
|
||||
Undirected
|
||||
Color bits.Bits
|
||||
N0 int
|
||||
}
|
||||
|
||||
// Subgraph represents a subgraph mapped to a supergraph.
|
||||
//
|
||||
// The subgraph is the embedded AdjacencyList and so the Subgraph type inherits
|
||||
// all methods of Adjacency list.
|
||||
//
|
||||
// The embedded subgraph mapped relative to a specific supergraph, member
|
||||
// Super. A subgraph may have fewer nodes than its supergraph.
|
||||
// Each node of the subgraph must map to a distinct node of the supergraph.
|
||||
//
|
||||
// The mapping giving the supergraph node for a given subgraph node is
|
||||
// represented by member SuperNI, a slice parallel to the the subgraph.
|
||||
//
|
||||
// The mapping in the other direction, giving a subgraph NI for a given
|
||||
// supergraph NI, is represented with map SubNI.
|
||||
//
|
||||
// Multiple Subgraphs can be created relative to a single supergraph.
|
||||
// The Subgraph type represents a mapping to only a single supergraph however.
|
||||
//
|
||||
// See graph methods InduceList and InduceBits for construction of
|
||||
// node-induced subgraphs.
|
||||
//
|
||||
// Alternatively an empty subgraph can be constructed with InduceList(nil).
|
||||
// Arbitrary subgraphs can then be built up with methods AddNode and AddArc.
|
||||
type Subgraph struct {
|
||||
AdjacencyList // the subgraph
|
||||
Super *AdjacencyList // the supergraph
|
||||
SubNI map[NI]NI // subgraph NIs, indexed by supergraph NIs
|
||||
SuperNI []NI // supergraph NIs indexed by subgraph NIs
|
||||
}
|
||||
|
||||
// DirectedSubgraph represents a subgraph mapped to a supergraph.
|
||||
//
|
||||
// See additional doc at Subgraph type.
|
||||
type DirectedSubgraph struct {
|
||||
Directed
|
||||
Super *Directed
|
||||
SubNI map[NI]NI
|
||||
SuperNI []NI
|
||||
}
|
||||
|
||||
// UndirectedSubgraph represents a subgraph mapped to a supergraph.
|
||||
//
|
||||
// See additional doc at Subgraph type.
|
||||
type UndirectedSubgraph struct {
|
||||
Undirected
|
||||
Super *Undirected
|
||||
SubNI map[NI]NI
|
||||
SuperNI []NI
|
||||
}
|
||||
|
||||
// LI is a label integer, used for associating labels with arcs.
|
||||
type LI int32
|
||||
|
||||
// Half is a half arc, representing a labeled arc and the "neighbor" node
|
||||
// that the arc leads to.
|
||||
//
|
||||
// Halfs can be composed to form a labeled adjacency list.
|
||||
type Half struct {
|
||||
To NI // node ID, usable as a slice index
|
||||
Label LI // half-arc ID for application data, often a weight
|
||||
}
|
||||
|
||||
// fromHalf is a half arc, representing a labeled arc and the "neighbor" node
|
||||
// that the arc originates from.
|
||||
//
|
||||
// This used internally in a couple of places. It used to be exported but is
|
||||
// not currently needed anwhere in the API.
|
||||
type fromHalf struct {
|
||||
From NI
|
||||
Label LI
|
||||
}
|
||||
|
||||
// A LabeledAdjacencyList represents a graph as a list of neighbors for each
|
||||
// node, connected by labeled arcs.
|
||||
//
|
||||
// Arc labels are not necessarily unique arc IDs. Different arcs can have
|
||||
// the same label.
|
||||
//
|
||||
// Arc labels are commonly used to assocate a weight with an arc. Arc labels
|
||||
// are general purpose however and can be used to associate arbitrary
|
||||
// information with an arc.
|
||||
//
|
||||
// Methods implementing weighted graph algorithms will commonly take a
|
||||
// weight function that turns a label int into a float64 weight.
|
||||
//
|
||||
// If only a small amount of information -- such as an integer weight or
|
||||
// a single printable character -- needs to be associated, it can sometimes
|
||||
// be possible to encode the information directly into the label int. For
|
||||
// more generality, some lookup scheme will be needed.
|
||||
//
|
||||
// In an undirected labeled graph, reciprocal arcs must have identical labels.
|
||||
// Note this does not preclude parallel arcs with different labels.
|
||||
type LabeledAdjacencyList [][]Half
|
||||
|
||||
// LabeledDirected represents a directed labeled graph.
|
||||
//
|
||||
// This is the labeled version of Directed. See types LabeledAdjacencyList
|
||||
// and Directed.
|
||||
type LabeledDirected struct {
|
||||
LabeledAdjacencyList // embedded to include LabeledAdjacencyList methods
|
||||
}
|
||||
|
||||
// LabeledUndirected represents an undirected labeled graph.
|
||||
//
|
||||
// This is the labeled version of Undirected. See types LabeledAdjacencyList
|
||||
// and Undirected.
|
||||
type LabeledUndirected struct {
|
||||
LabeledAdjacencyList // embedded to include LabeledAdjacencyList methods
|
||||
}
|
||||
|
||||
// LabeledBipartite represents a bipartite graph.
|
||||
//
|
||||
// In a bipartite graph, nodes are partitioned into two sets, or
|
||||
// "colors," such that every edge in the graph goes from one set to the
|
||||
// other.
|
||||
//
|
||||
// Member Color represents the partition with a bitmap of length the same
|
||||
// as the number of nodes in the graph. For convenience N0 stores the number
|
||||
// of zero bits in Color.
|
||||
//
|
||||
// To construct a LabeledBipartite object, if you can easily or efficiently use
|
||||
// available information to construct the Color member, then you should do
|
||||
// this and construct a LabeledBipartite object with a Go struct literal.
|
||||
//
|
||||
// If partition information is not readily available, see the constructor
|
||||
// Undirected.LabeledBipartite.
|
||||
//
|
||||
// Alternatively, in some cases where the graph may have multiple connected
|
||||
// components, the lower level LabeledUndirected.BipartiteComponent can be used
|
||||
// to control color assignment by component.
|
||||
type LabeledBipartite struct {
|
||||
LabeledUndirected
|
||||
Color bits.Bits
|
||||
N0 int
|
||||
}
|
||||
|
||||
// LabeledSubgraph represents a subgraph mapped to a supergraph.
|
||||
//
|
||||
// See additional doc at Subgraph type.
|
||||
type LabeledSubgraph struct {
|
||||
LabeledAdjacencyList
|
||||
Super *LabeledAdjacencyList
|
||||
SubNI map[NI]NI
|
||||
SuperNI []NI
|
||||
}
|
||||
|
||||
// LabeledDirectedSubgraph represents a subgraph mapped to a supergraph.
|
||||
//
|
||||
// See additional doc at Subgraph type.
|
||||
type LabeledDirectedSubgraph struct {
|
||||
LabeledDirected
|
||||
Super *LabeledDirected
|
||||
SubNI map[NI]NI
|
||||
SuperNI []NI
|
||||
}
|
||||
|
||||
// LabeledUndirectedSubgraph represents a subgraph mapped to a supergraph.
|
||||
//
|
||||
// See additional doc at Subgraph type.
|
||||
type LabeledUndirectedSubgraph struct {
|
||||
LabeledUndirected
|
||||
Super *LabeledUndirected
|
||||
SubNI map[NI]NI
|
||||
SuperNI []NI
|
||||
}
|
||||
|
||||
// Edge is an undirected edge between nodes N1 and N2.
|
||||
type Edge struct{ N1, N2 NI }
|
||||
|
||||
// LabeledEdge is an undirected edge with an associated label.
|
||||
type LabeledEdge struct {
|
||||
Edge
|
||||
LI
|
||||
}
|
||||
|
||||
// LabeledPath is a start node and a path of half arcs leading from start.
|
||||
type LabeledPath struct {
|
||||
Start NI
|
||||
Path []Half
|
||||
}
|
||||
|
||||
// Distance returns total path distance given WeightFunc w.
|
||||
func (p LabeledPath) Distance(w WeightFunc) float64 {
|
||||
d := 0.
|
||||
for _, h := range p.Path {
|
||||
d += w(h.Label)
|
||||
}
|
||||
return d
|
||||
}
|
||||
|
||||
// WeightFunc returns a weight for a given label.
|
||||
//
|
||||
// WeightFunc is a parameter type for various search functions. The intent
|
||||
// is to return a weight corresponding to an arc label. The name "weight"
|
||||
// is an abstract term. An arc "weight" will typically have some application
|
||||
// specific meaning other than physical weight.
|
||||
type WeightFunc func(label LI) (weight float64)
|
||||
|
||||
// WeightedEdgeList is a graph representation.
|
||||
//
|
||||
// It is a labeled edge list, with an associated weight function to return
|
||||
// a weight given an edge label.
|
||||
//
|
||||
// Also associated is the order, or number of nodes of the graph.
|
||||
// All nodes occurring in the edge list must be strictly less than Order.
|
||||
//
|
||||
// WeigtedEdgeList sorts by weight, obtained by calling the weight function.
|
||||
// If weight computation is expensive, consider supplying a cached or
|
||||
// memoized version.
|
||||
type WeightedEdgeList struct {
|
||||
Order int
|
||||
WeightFunc
|
||||
Edges []LabeledEdge
|
||||
}
|
||||
|
||||
// DistanceMatrix constructs a distance matrix corresponding to the weighted
|
||||
// edges of l.
|
||||
//
|
||||
// An edge n1, n2 with WeightFunc return w is represented by both
|
||||
// d[n1][n2] == w and d[n2][n1] = w. In case of parallel edges, the lowest
|
||||
// weight is stored. The distance from any node to itself d[n][n] is 0, unless
|
||||
// the node has a loop with a negative weight. If g has no edge between n1 and
|
||||
// distinct n2, +Inf is stored for d[n1][n2] and d[n2][n1].
|
||||
//
|
||||
// The returned DistanceMatrix is suitable for DistanceMatrix.FloydWarshall.
|
||||
func (l WeightedEdgeList) DistanceMatrix() (d DistanceMatrix) {
|
||||
d = newDM(l.Order)
|
||||
for _, e := range l.Edges {
|
||||
n1 := e.Edge.N1
|
||||
n2 := e.Edge.N2
|
||||
wt := l.WeightFunc(e.LI)
|
||||
// < to pick min of parallel arcs (also nicely ignores NaN)
|
||||
if wt < d[n1][n2] {
|
||||
d[n1][n2] = wt
|
||||
d[n2][n1] = wt
|
||||
}
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
// A DistanceMatrix is a square matrix representing some distance between
|
||||
// nodes of a graph. If the graph is directected, d[from][to] represents
|
||||
// some distance from node 'from' to node 'to'. Depending on context, the
|
||||
// distance may be an arc weight or path distance. A value of +Inf typically
|
||||
// means no arc or no path between the nodes.
|
||||
type DistanceMatrix [][]float64
|
||||
|
||||
// little helper function, makes a blank distance matrix for FloydWarshall.
|
||||
// could be exported?
|
||||
func newDM(n int) DistanceMatrix {
|
||||
inf := math.Inf(1)
|
||||
d := make(DistanceMatrix, n)
|
||||
for i := range d {
|
||||
di := make([]float64, n)
|
||||
for j := range di {
|
||||
di[j] = inf
|
||||
}
|
||||
di[i] = 0
|
||||
d[i] = di
|
||||
}
|
||||
return d
|
||||
}
|
||||
|
||||
// FloydWarshall finds all pairs shortest distances for a weighted graph
|
||||
// without negative cycles.
|
||||
//
|
||||
// It operates on a distance matrix representing arcs of a graph and
|
||||
// destructively replaces arc weights with shortest path distances.
|
||||
//
|
||||
// In receiver d, d[fr][to] will be the shortest distance from node
|
||||
// 'fr' to node 'to'. An element value of +Inf means no path exists.
|
||||
// Any diagonal element < 0 indicates a negative cycle exists.
|
||||
//
|
||||
// See DistanceMatrix constructor methods of LabeledAdjacencyList and
|
||||
// WeightedEdgeList for suitable inputs.
|
||||
func (d DistanceMatrix) FloydWarshall() {
|
||||
for k, dk := range d {
|
||||
for _, di := range d {
|
||||
dik := di[k]
|
||||
for j := range d {
|
||||
if d2 := dik + dk[j]; d2 < di[j] {
|
||||
di[j] = d2
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// PathMatrix is a return type for FloydWarshallPaths.
|
||||
//
|
||||
// It encodes all pairs shortest paths.
|
||||
type PathMatrix [][]NI
|
||||
|
||||
// Path returns a shortest path from node start to end.
|
||||
//
|
||||
// Argument p is truncated, appended to, and returned as the result.
|
||||
// Thus the underlying allocation is reused if possible.
|
||||
// If there is no path from start to end, p is returned truncated to
|
||||
// zero length.
|
||||
//
|
||||
// If receiver m is not a valid populated PathMatrix as returned by
|
||||
// FloydWarshallPaths, behavior is undefined and a panic is likely.
|
||||
func (m PathMatrix) Path(start, end NI, p []NI) []NI {
|
||||
p = p[:0]
|
||||
for {
|
||||
p = append(p, start)
|
||||
if start == end {
|
||||
return p
|
||||
}
|
||||
start = m[start][end]
|
||||
if start < 0 {
|
||||
return p[:0]
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// FloydWarshallPaths finds all pairs shortest paths for a weighted graph
|
||||
// without negative cycles.
|
||||
//
|
||||
// It operates on a distance matrix representing arcs of a graph and
|
||||
// destructively replaces arc weights with shortest path distances.
|
||||
//
|
||||
// In receiver d, d[fr][to] will be the shortest distance from node
|
||||
// 'fr' to node 'to'. An element value of +Inf means no path exists.
|
||||
// Any diagonal element < 0 indicates a negative cycle exists.
|
||||
//
|
||||
// The return value encodes the paths. See PathMatrix.Path.
|
||||
//
|
||||
// See DistanceMatrix constructor methods of LabeledAdjacencyList and
|
||||
// WeightedEdgeList for suitable inputs.
|
||||
//
|
||||
// See also similar method FloydWarshallFromLists which has a richer
|
||||
// return value.
|
||||
func (d DistanceMatrix) FloydWarshallPaths() PathMatrix {
|
||||
m := make(PathMatrix, len(d))
|
||||
inf := math.Inf(1)
|
||||
for i, di := range d {
|
||||
mi := make([]NI, len(d))
|
||||
for j, dij := range di {
|
||||
if dij == inf {
|
||||
mi[j] = -1
|
||||
} else {
|
||||
mi[j] = NI(j)
|
||||
}
|
||||
}
|
||||
m[i] = mi
|
||||
}
|
||||
for k, dk := range d {
|
||||
for i, di := range d {
|
||||
mi := m[i]
|
||||
dik := di[k]
|
||||
for j := range d {
|
||||
if d2 := dik + dk[j]; d2 < di[j] {
|
||||
di[j] = d2
|
||||
mi[j] = mi[k]
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return m
|
||||
}
|
||||
|
||||
// FloydWarshallFromLists finds all pairs shortest paths for a weighted
|
||||
// graph without negative cycles.
|
||||
//
|
||||
// It operates on a distance matrix representing arcs of a graph and
|
||||
// destructively replaces arc weights with shortest path distances.
|
||||
//
|
||||
// In receiver d, d[fr][to] will be the shortest distance from node
|
||||
// 'fr' to node 'to'. An element value of +Inf means no path exists.
|
||||
// Any diagonal element < 0 indicates a negative cycle exists.
|
||||
//
|
||||
// The return value encodes the paths. The FromLists are fully populated
|
||||
// with Leaves and Len values. See for example FromList.PathTo for
|
||||
// extracting paths. Note though that for i'th FromList of the return
|
||||
// value, PathTo(j) will return the path from j's root, which will not
|
||||
// be i in the case that there is no path from i to j. You must check
|
||||
// the first node of the path to see if it is i. If not, there is no
|
||||
// path from i to j. See example.
|
||||
//
|
||||
// See DistanceMatrix constructor methods of LabeledAdjacencyList and
|
||||
// WeightedEdgeList for suitable inputs.
|
||||
//
|
||||
// See also similar method FloydWarshallPaths, which has a lighter
|
||||
// weight return value.
|
||||
func (d DistanceMatrix) FloydWarshallFromLists() []FromList {
|
||||
l := make([]FromList, len(d))
|
||||
inf := math.Inf(1)
|
||||
for i, di := range d {
|
||||
li := NewFromList(len(d))
|
||||
p := li.Paths
|
||||
for j, dij := range di {
|
||||
if i == j || dij == inf {
|
||||
p[j] = PathEnd{From: -1}
|
||||
} else {
|
||||
p[j] = PathEnd{From: NI(i)}
|
||||
}
|
||||
}
|
||||
l[i] = li
|
||||
}
|
||||
for k, dk := range d {
|
||||
pk := l[k].Paths
|
||||
for i, di := range d {
|
||||
dik := di[k]
|
||||
pi := l[i].Paths
|
||||
for j := range d {
|
||||
if d2 := dik + dk[j]; d2 < di[j] {
|
||||
di[j] = d2
|
||||
pi[j] = pk[j]
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
for _, li := range l {
|
||||
li.RecalcLeaves()
|
||||
li.RecalcLen()
|
||||
}
|
||||
return l
|
||||
}
|
||||
|
||||
// AddEdge adds an edge to a subgraph.
|
||||
//
|
||||
// For argument e, e.N1 and e.N2 must be NIs in supergraph s.Super. As with
|
||||
// AddNode, AddEdge panics if e.N1 and e.N2 are not valid node indexes of
|
||||
// s.Super.
|
||||
//
|
||||
// Edge e must exist in s.Super. Further, the number of
|
||||
// parallel edges in the subgraph cannot exceed the number of corresponding
|
||||
// parallel edges in the supergraph. That is, each edge already added to the
|
||||
// subgraph counts against the edges available in the supergraph. If a matching
|
||||
// edge is not available, AddEdge returns an error.
|
||||
//
|
||||
// If a matching edge is available, subgraph nodes are added as needed, the
|
||||
// subgraph edge is added, and the method returns nil.
|
||||
func (s *UndirectedSubgraph) AddEdge(n1, n2 NI) error {
|
||||
// verify supergraph NIs first, but without adding subgraph nodes just yet.
|
||||
if int(n1) < 0 || int(n1) >= s.Super.Order() {
|
||||
panic(fmt.Sprint("AddEdge: NI ", n1, " not in supergraph"))
|
||||
}
|
||||
if int(n2) < 0 || int(n2) >= s.Super.Order() {
|
||||
panic(fmt.Sprint("AddEdge: NI ", n2, " not in supergraph"))
|
||||
}
|
||||
// count existing matching edges in subgraph
|
||||
n := 0
|
||||
a := s.Undirected.AdjacencyList
|
||||
if b1, ok := s.SubNI[n1]; ok {
|
||||
if b2, ok := s.SubNI[n2]; ok {
|
||||
// both NIs already exist in subgraph, need to count edges
|
||||
for _, t := range a[b1] {
|
||||
if t == b2 {
|
||||
n++
|
||||
}
|
||||
}
|
||||
if b1 != b2 {
|
||||
// verify reciprocal arcs exist
|
||||
r := 0
|
||||
for _, t := range a[b2] {
|
||||
if t == b1 {
|
||||
r++
|
||||
}
|
||||
}
|
||||
if r < n {
|
||||
n = r
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
// verify matching edges are available in supergraph
|
||||
m := 0
|
||||
for _, t := range (*s.Super).AdjacencyList[n1] {
|
||||
if t == n2 {
|
||||
if m == n {
|
||||
goto r // arc match after all existing arcs matched
|
||||
}
|
||||
m++
|
||||
}
|
||||
}
|
||||
return errors.New("edge not available in supergraph")
|
||||
r:
|
||||
if n1 != n2 {
|
||||
// verify reciprocal arcs
|
||||
m = 0
|
||||
for _, t := range (*s.Super).AdjacencyList[n2] {
|
||||
if t == n1 {
|
||||
if m == n {
|
||||
goto good
|
||||
}
|
||||
m++
|
||||
}
|
||||
}
|
||||
return errors.New("edge not available in supergraph")
|
||||
}
|
||||
good:
|
||||
// matched enough edges. nodes can finally
|
||||
// be added as needed and then the edge can be added.
|
||||
b1 := s.AddNode(n1)
|
||||
b2 := s.AddNode(n2)
|
||||
s.Undirected.AddEdge(b1, b2)
|
||||
return nil // success
|
||||
}
|
||||
|
||||
// AddEdge adds an edge to a subgraph.
|
||||
//
|
||||
// For argument e, e.N1 and e.N2 must be NIs in supergraph s.Super. As with
|
||||
// AddNode, AddEdge panics if e.N1 and e.N2 are not valid node indexes of
|
||||
// s.Super.
|
||||
//
|
||||
// Edge e must exist in s.Super with label l. Further, the number of
|
||||
// parallel edges in the subgraph cannot exceed the number of corresponding
|
||||
// parallel edges in the supergraph. That is, each edge already added to the
|
||||
// subgraph counts against the edges available in the supergraph. If a matching
|
||||
// edge is not available, AddEdge returns an error.
|
||||
//
|
||||
// If a matching edge is available, subgraph nodes are added as needed, the
|
||||
// subgraph edge is added, and the method returns nil.
|
||||
func (s *LabeledUndirectedSubgraph) AddEdge(e Edge, l LI) error {
|
||||
// verify supergraph NIs first, but without adding subgraph nodes just yet.
|
||||
if int(e.N1) < 0 || int(e.N1) >= s.Super.Order() {
|
||||
panic(fmt.Sprint("AddEdge: NI ", e.N1, " not in supergraph"))
|
||||
}
|
||||
if int(e.N2) < 0 || int(e.N2) >= s.Super.Order() {
|
||||
panic(fmt.Sprint("AddEdge: NI ", e.N2, " not in supergraph"))
|
||||
}
|
||||
// count existing matching edges in subgraph
|
||||
n := 0
|
||||
a := s.LabeledUndirected.LabeledAdjacencyList
|
||||
if b1, ok := s.SubNI[e.N1]; ok {
|
||||
if b2, ok := s.SubNI[e.N2]; ok {
|
||||
// both NIs already exist in subgraph, need to count edges
|
||||
h := Half{b2, l}
|
||||
for _, t := range a[b1] {
|
||||
if t == h {
|
||||
n++
|
||||
}
|
||||
}
|
||||
if b1 != b2 {
|
||||
// verify reciprocal arcs exist
|
||||
r := 0
|
||||
h.To = b1
|
||||
for _, t := range a[b2] {
|
||||
if t == h {
|
||||
r++
|
||||
}
|
||||
}
|
||||
if r < n {
|
||||
n = r
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
// verify matching edges are available in supergraph
|
||||
m := 0
|
||||
h := Half{e.N2, l}
|
||||
for _, t := range (*s.Super).LabeledAdjacencyList[e.N1] {
|
||||
if t == h {
|
||||
if m == n {
|
||||
goto r // arc match after all existing arcs matched
|
||||
}
|
||||
m++
|
||||
}
|
||||
}
|
||||
return errors.New("edge not available in supergraph")
|
||||
r:
|
||||
if e.N1 != e.N2 {
|
||||
// verify reciprocal arcs
|
||||
m = 0
|
||||
h.To = e.N1
|
||||
for _, t := range (*s.Super).LabeledAdjacencyList[e.N2] {
|
||||
if t == h {
|
||||
if m == n {
|
||||
goto good
|
||||
}
|
||||
m++
|
||||
}
|
||||
}
|
||||
return errors.New("edge not available in supergraph")
|
||||
}
|
||||
good:
|
||||
// matched enough edges. nodes can finally
|
||||
// be added as needed and then the edge can be added.
|
||||
n1 := s.AddNode(e.N1)
|
||||
n2 := s.AddNode(e.N2)
|
||||
s.LabeledUndirected.AddEdge(Edge{n1, n2}, l)
|
||||
return nil // success
|
||||
}
|
||||
|
||||
// utility function called from all of the InduceList methods.
|
||||
func mapList(l []NI) (sub map[NI]NI, sup []NI) {
|
||||
sub = map[NI]NI{}
|
||||
// one pass to collect unique NIs
|
||||
for _, p := range l {
|
||||
sub[NI(p)] = -1
|
||||
}
|
||||
if len(sub) == len(l) { // NIs in l are unique
|
||||
sup = append([]NI{}, l...) // just copy them
|
||||
for b, p := range l {
|
||||
sub[p] = NI(b) // and fill in map
|
||||
}
|
||||
} else { // NIs in l not unique
|
||||
sup = make([]NI, 0, len(sub))
|
||||
for _, p := range l { // preserve ordering of first occurrences in l
|
||||
if sub[p] < 0 {
|
||||
sub[p] = NI(len(sup))
|
||||
sup = append(sup, p)
|
||||
}
|
||||
}
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
// utility function called from all of the InduceBits methods.
|
||||
func mapBits(t bits.Bits) (sub map[NI]NI, sup []NI) {
|
||||
sup = make([]NI, 0, t.OnesCount())
|
||||
sub = make(map[NI]NI, cap(sup))
|
||||
t.IterateOnes(func(n int) bool {
|
||||
sub[NI(n)] = NI(len(sup))
|
||||
sup = append(sup, NI(n))
|
||||
return true
|
||||
})
|
||||
return
|
||||
}
|
||||
|
||||
// OrderMap formats maps for testable examples.
|
||||
//
|
||||
// OrderMap provides simple, no-frills formatting of maps in sorted order,
|
||||
// convenient in some cases for output of testable examples.
|
||||
func OrderMap(m interface{}) string {
|
||||
// in particular exclude slices, which template would happily accept but
|
||||
// which would probably represent a coding mistake
|
||||
if reflect.TypeOf(m).Kind() != reflect.Map {
|
||||
panic("not a map")
|
||||
}
|
||||
t := template.Must(template.New("").Parse(
|
||||
`map[{{range $k, $v := .}}{{$k}}:{{$v}} {{end}}]`))
|
||||
var b bytes.Buffer
|
||||
if err := t.Execute(&b, m); err != nil {
|
||||
panic(err)
|
||||
}
|
||||
return b.String()
|
||||
}
|
135
vendor/github.com/soniakeys/graph/hacking.adoc
generated
vendored
Normal file
135
vendor/github.com/soniakeys/graph/hacking.adoc
generated
vendored
Normal file
|
@ -0,0 +1,135 @@
|
|||
= Hacking
|
||||
|
||||
== Get, install
|
||||
Basic use of the package is just go get, or git clone; go install. There are
|
||||
no dependencies outside the standard library.
|
||||
|
||||
== Build
|
||||
CI is currently on travis-ci.org.
|
||||
|
||||
The build runs go vet with a few exceptions for things I'm not a big fan of.
|
||||
|
||||
https://github.com/client9/misspell has been valuable.
|
||||
|
||||
Also I wrote https://github.com/soniakeys/vetc to validate that each source
|
||||
file has copyright/license statement.
|
||||
|
||||
Then, it’s not in the ci script, but I wrote https://github.com/soniakeys/rcv
|
||||
to put coverage stats in the readme. Maybe it could be commit hook or
|
||||
something but for now I’ll try just running it manually now and then.
|
||||
|
||||
Go fmt is not in the ci script, but I have at least one editor set up to run
|
||||
it on save, so code should stay formatted pretty well.
|
||||
|
||||
== Examples with random output
|
||||
The math/rand generators with constant seeds used to give consistent numbers
|
||||
across Go versions and so some examples relied on this. Sometime after Go 1.9
|
||||
though the numbers changed. The technique for now is to go ahead and write
|
||||
those examples, get them working, then change the `// Output:` line to
|
||||
`// Random output:`. This keeps them showing in go doc but keeps them from
|
||||
being run by go test. This works for now. It might be revisited at some
|
||||
point.
|
||||
|
||||
== Plans
|
||||
The primary to-do list is the issue tracker on Github.
|
||||
|
||||
== Direction, focus, features
|
||||
The project started with no real goal or purpose, just as a place for some code
|
||||
that might be useful. Here are some elements that characterize the direction.
|
||||
|
||||
* The focus has been on algorithms on adjacency lists. That is, adjacency list
|
||||
is the fundamental representation for most implemented algorithms. There are
|
||||
many other interesting representations, many reasons to use them, but
|
||||
adjacency list is common in literature and practice. It has been useful to
|
||||
focus on this data representation, at first anyway.
|
||||
|
||||
* The focus has been on single threaded algorithms. Again, there is much new
|
||||
and interesting work being done with concurrent, parallel, and distributed
|
||||
graph algorithms, and Go might be an excellent language to implement some of
|
||||
these algorithms. But as a preliminary step, more traditional
|
||||
single-threaded algorithms are implemented.
|
||||
|
||||
* The focus has been on static finite graphs. Again there is much interesting
|
||||
work in online algorithms, dynamic graphs, and infinite graphs, but these
|
||||
are not generally considered here.
|
||||
|
||||
* Algorithms selected for implementation are generally ones commonly appearing
|
||||
in beginning graph theory discussions and in general purpose graph libraries
|
||||
in other programming languages. With these as drivers, there's a big risk
|
||||
developing a library of curiosities and academic exercises rather than a
|
||||
library of practical utility. But well, it's a start. The hope is that
|
||||
there are some practical drivers behind graph theory and behind other graph
|
||||
libraries.
|
||||
|
||||
* There is active current research going on in graph algorithm development.
|
||||
One goal for this library is to implement newer and faster algorithms.
|
||||
In some cases where it seems not too much work, older/classic/traditional
|
||||
algorithms may be implemented for comparison. These generally go in the
|
||||
alt subdirectory.
|
||||
|
||||
== General principles
|
||||
* The API is rather low level.
|
||||
|
||||
* Slices instead of maps. Maps are pretty efficient, and the property of
|
||||
unique keys can be useful, But slices are still faster and more efficient,
|
||||
and the unique key property is not always needed or wanted. The Adjacency
|
||||
list implementation of this library is all done in slices. Slices are used
|
||||
in algorithms where possible, in preference to maps. Maps are still used in
|
||||
some cases where uniqueness is needed.
|
||||
|
||||
* Interfaces not generally used. Algorithms are implemented directly on
|
||||
concrete data types and not on interfaces describing the capabilities of
|
||||
the data types. The abstraction of interfaces is a nice match to graph
|
||||
theory and the convenience of running graph algorithms on any type that
|
||||
implements an interface is appealing, but the costs seem too high to me.
|
||||
Slices are rich with capababilites that get hidden behind interfaces and
|
||||
direct slice manipulation is always faster than going through interfaces.
|
||||
An impedance for programs using the library is that they will generally
|
||||
have to implement a mapping from slice indexes to their application data,
|
||||
often including for example, some other form of node ID. This seems fair
|
||||
to push this burden outside the graph library; the library cannot know
|
||||
the needs of this mapping.
|
||||
|
||||
* Bitsets are widely used, particularly to store one bit of information per
|
||||
node of a graph. I used math/big at first but then moved to a dense bitset
|
||||
of my own. Yes, I considered other third-party bitsets but had my own
|
||||
feature set I wanted. A slice of bools is another alternative. Bools will
|
||||
be faster in almost all cases but the bitset will use less memory. I'm
|
||||
chosing size over speed for now.
|
||||
|
||||
* Code generation is used to provide methods that work on both labeled and
|
||||
unlabeled graphs. Code is written to labeled types, then transformations
|
||||
generate the unlabled equivalents.
|
||||
|
||||
* Methods are named for what they return rather than what they do, where
|
||||
reasonable anyway.
|
||||
|
||||
* Consistency in method signature and behavior across corresponding methods,
|
||||
for example directed/undirected, labeled/unlabeled, again, as long as it's
|
||||
reasonable.
|
||||
|
||||
* Sometimes in tension with the consistency principle, methods are lazy about
|
||||
datatypes of parameters and return values. Sometimes a vale might have
|
||||
different reasonable representations, a set might be a bitset, map, slice
|
||||
of bools, or slice of set members for example. Methods will take and return
|
||||
whatever is convenient for them and not convert the form just for consistency
|
||||
or to try to guess what a caller would prefer.
|
||||
|
||||
* Methods return multiple results for whatever the algorithm produces that
|
||||
might be of interest. Sometimes an algorithm will have a primary result but
|
||||
then some secondary values that also might be of interest. If they are
|
||||
already computed as a byproduct of the algorithm, or can be computed at
|
||||
negligible cost, return them.
|
||||
|
||||
* Sometimes in conflict with the multiple result principle, methods will not
|
||||
speculatively compute secondary results if there is any significant cost
|
||||
and if the secondary result can be just as easily computed later.
|
||||
|
||||
== Code Maintenance
|
||||
There are tons of cut and paste variants. There's the basic AdjacencyList,
|
||||
then Directed and Undirected variants, then Labeled variants of each of those.
|
||||
Code gen helps avoid some cut and paste but there's a bunch that doesn't
|
||||
code gen very well and so is duplicated with cut and paste. In particular
|
||||
the testable examples in the _test files don't cg well and so are pretty much
|
||||
all duplicated by hand. If you change code, think about where there should
|
||||
be variants and go look to see if the variants need similar changes.
|
254
vendor/github.com/soniakeys/graph/mst.go
generated
vendored
Normal file
254
vendor/github.com/soniakeys/graph/mst.go
generated
vendored
Normal file
|
@ -0,0 +1,254 @@
|
|||
// Copyright 2014 Sonia Keys
|
||||
// License MIT: http://opensource.org/licenses/MIT
|
||||
|
||||
package graph
|
||||
|
||||
import (
|
||||
"container/heap"
|
||||
"sort"
|
||||
|
||||
"github.com/soniakeys/bits"
|
||||
)
|
||||
|
||||
type dsElement struct {
|
||||
from NI
|
||||
rank int
|
||||
}
|
||||
|
||||
type disjointSet struct {
|
||||
set []dsElement
|
||||
}
|
||||
|
||||
func newDisjointSet(n int) disjointSet {
|
||||
set := make([]dsElement, n)
|
||||
for i := range set {
|
||||
set[i].from = -1
|
||||
}
|
||||
return disjointSet{set}
|
||||
}
|
||||
|
||||
// return true if disjoint trees were combined.
|
||||
// false if x and y were already in the same tree.
|
||||
func (ds disjointSet) union(x, y NI) bool {
|
||||
xr := ds.find(x)
|
||||
yr := ds.find(y)
|
||||
if xr == yr {
|
||||
return false
|
||||
}
|
||||
switch xe, ye := &ds.set[xr], &ds.set[yr]; {
|
||||
case xe.rank < ye.rank:
|
||||
xe.from = yr
|
||||
case xe.rank == ye.rank:
|
||||
xe.rank++
|
||||
fallthrough
|
||||
default:
|
||||
ye.from = xr
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
func (ds disjointSet) find(n NI) NI {
|
||||
// fast paths for n == root or from root.
|
||||
// no updates need in these cases.
|
||||
s := ds.set
|
||||
fr := s[n].from
|
||||
if fr < 0 { // n is root
|
||||
return n
|
||||
}
|
||||
n, fr = fr, s[fr].from
|
||||
if fr < 0 { // n is from root
|
||||
return n
|
||||
}
|
||||
// otherwise updates needed.
|
||||
// two iterative passes (rather than recursion or stack)
|
||||
// pass 1: find root
|
||||
r := fr
|
||||
for {
|
||||
f := s[r].from
|
||||
if f < 0 {
|
||||
break
|
||||
}
|
||||
r = f
|
||||
}
|
||||
// pass 2: update froms
|
||||
for {
|
||||
s[n].from = r
|
||||
if fr == r {
|
||||
return r
|
||||
}
|
||||
n = fr
|
||||
fr = s[n].from
|
||||
}
|
||||
}
|
||||
|
||||
// Kruskal implements Kruskal's algorithm for constructing a minimum spanning
|
||||
// forest on an undirected graph.
|
||||
//
|
||||
// The forest is returned as an undirected graph.
|
||||
//
|
||||
// Also returned is a total distance for the returned forest.
|
||||
//
|
||||
// This method is a convenience wrapper for LabeledEdgeList.Kruskal.
|
||||
// If you have no need for the input graph as a LabeledUndirected, it may be
|
||||
// more efficient to construct a LabeledEdgeList directly.
|
||||
func (g LabeledUndirected) Kruskal(w WeightFunc) (spanningForest LabeledUndirected, dist float64) {
|
||||
return g.WeightedArcsAsEdges(w).Kruskal()
|
||||
}
|
||||
|
||||
// Kruskal implements Kruskal's algorithm for constructing a minimum spanning
|
||||
// forest on an undirected graph.
|
||||
//
|
||||
// The algorithm allows parallel edges, thus it is acceptable to construct
|
||||
// the receiver with LabeledUndirected.WeightedArcsAsEdges. It may be more
|
||||
// efficient though, if you can construct the receiver WeightedEdgeList
|
||||
// directly without parallel edges.
|
||||
//
|
||||
// The forest is returned as an undirected graph.
|
||||
//
|
||||
// Also returned is a total distance for the returned forest.
|
||||
//
|
||||
// The edge list of the receiver is sorted in place as a side effect of this
|
||||
// method. See KruskalSorted for a version that relies on the edge list being
|
||||
// already sorted. This method is a wrapper for KruskalSorted. If you can
|
||||
// generate the input graph sorted as required for KruskalSorted, you can
|
||||
// call that method directly and avoid the overhead of the sort.
|
||||
func (l WeightedEdgeList) Kruskal() (g LabeledUndirected, dist float64) {
|
||||
e := l.Edges
|
||||
w := l.WeightFunc
|
||||
sort.Slice(e, func(i, j int) bool { return w(e[i].LI) < w(e[j].LI) })
|
||||
return l.KruskalSorted()
|
||||
}
|
||||
|
||||
// KruskalSorted implements Kruskal's algorithm for constructing a minimum
|
||||
// spanning tree on an undirected graph.
|
||||
//
|
||||
// When called, the edge list of the receiver must be already sorted by weight.
|
||||
// See the Kruskal method for a version that accepts an unsorted edge list.
|
||||
// As with Kruskal, parallel edges are allowed.
|
||||
//
|
||||
// The forest is returned as an undirected graph.
|
||||
//
|
||||
// Also returned is a total distance for the returned forest.
|
||||
func (l WeightedEdgeList) KruskalSorted() (g LabeledUndirected, dist float64) {
|
||||
ds := newDisjointSet(l.Order)
|
||||
g.LabeledAdjacencyList = make(LabeledAdjacencyList, l.Order)
|
||||
for _, e := range l.Edges {
|
||||
if ds.union(e.N1, e.N2) {
|
||||
g.AddEdge(Edge{e.N1, e.N2}, e.LI)
|
||||
dist += l.WeightFunc(e.LI)
|
||||
}
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
// Prim implements the Jarník-Prim-Dijkstra algorithm for constructing
|
||||
// a minimum spanning tree on an undirected graph.
|
||||
//
|
||||
// Prim computes a minimal spanning tree on the connected component containing
|
||||
// the given start node. The tree is returned in FromList f. Argument f
|
||||
// cannot be a nil pointer although it can point to a zero value FromList.
|
||||
//
|
||||
// If the passed FromList.Paths has the len of g though, it will be reused.
|
||||
// In the case of a graph with multiple connected components, this allows a
|
||||
// spanning forest to be accumulated by calling Prim successively on
|
||||
// representative nodes of the components. In this case if leaves for
|
||||
// individual trees are of interest, pass a non-nil zero-value for the argument
|
||||
// componentLeaves and it will be populated with leaves for the single tree
|
||||
// spanned by the call.
|
||||
//
|
||||
// If argument labels is non-nil, it must have the same length as g and will
|
||||
// be populated with labels corresponding to the edges of the tree.
|
||||
//
|
||||
// Returned are the number of nodes spanned for the single tree (which will be
|
||||
// the order of the connected component) and the total spanned distance for the
|
||||
// single tree.
|
||||
func (g LabeledUndirected) Prim(start NI, w WeightFunc, f *FromList, labels []LI, componentLeaves *bits.Bits) (numSpanned int, dist float64) {
|
||||
al := g.LabeledAdjacencyList
|
||||
if len(f.Paths) != len(al) {
|
||||
*f = NewFromList(len(al))
|
||||
}
|
||||
if f.Leaves.Num != len(al) {
|
||||
f.Leaves = bits.New(len(al))
|
||||
}
|
||||
b := make([]prNode, len(al)) // "best"
|
||||
for n := range b {
|
||||
b[n].nx = NI(n)
|
||||
b[n].fx = -1
|
||||
}
|
||||
rp := f.Paths
|
||||
var frontier prHeap
|
||||
rp[start] = PathEnd{From: -1, Len: 1}
|
||||
numSpanned = 1
|
||||
fLeaves := &f.Leaves
|
||||
fLeaves.SetBit(int(start), 1)
|
||||
if componentLeaves != nil {
|
||||
if componentLeaves.Num != len(al) {
|
||||
*componentLeaves = bits.New(len(al))
|
||||
}
|
||||
componentLeaves.SetBit(int(start), 1)
|
||||
}
|
||||
for a := start; ; {
|
||||
for _, nb := range al[a] {
|
||||
if rp[nb.To].Len > 0 {
|
||||
continue // already in MST, no action
|
||||
}
|
||||
switch bp := &b[nb.To]; {
|
||||
case bp.fx == -1: // new node for frontier
|
||||
bp.from = fromHalf{From: a, Label: nb.Label}
|
||||
bp.wt = w(nb.Label)
|
||||
heap.Push(&frontier, bp)
|
||||
case w(nb.Label) < bp.wt: // better arc
|
||||
bp.from = fromHalf{From: a, Label: nb.Label}
|
||||
bp.wt = w(nb.Label)
|
||||
heap.Fix(&frontier, bp.fx)
|
||||
}
|
||||
}
|
||||
if len(frontier) == 0 {
|
||||
break // done
|
||||
}
|
||||
bp := heap.Pop(&frontier).(*prNode)
|
||||
a = bp.nx
|
||||
rp[a].Len = rp[bp.from.From].Len + 1
|
||||
rp[a].From = bp.from.From
|
||||
if len(labels) != 0 {
|
||||
labels[a] = bp.from.Label
|
||||
}
|
||||
dist += bp.wt
|
||||
fLeaves.SetBit(int(bp.from.From), 0)
|
||||
fLeaves.SetBit(int(a), 1)
|
||||
if componentLeaves != nil {
|
||||
componentLeaves.SetBit(int(bp.from.From), 0)
|
||||
componentLeaves.SetBit(int(a), 1)
|
||||
}
|
||||
numSpanned++
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
type prNode struct {
|
||||
nx NI
|
||||
from fromHalf
|
||||
wt float64 // p.Weight(from.Label)
|
||||
fx int
|
||||
}
|
||||
|
||||
type prHeap []*prNode
|
||||
|
||||
func (h prHeap) Len() int { return len(h) }
|
||||
func (h prHeap) Less(i, j int) bool { return h[i].wt < h[j].wt }
|
||||
func (h prHeap) Swap(i, j int) {
|
||||
h[i], h[j] = h[j], h[i]
|
||||
h[i].fx = i
|
||||
h[j].fx = j
|
||||
}
|
||||
func (p *prHeap) Push(x interface{}) {
|
||||
nd := x.(*prNode)
|
||||
nd.fx = len(*p)
|
||||
*p = append(*p, nd)
|
||||
}
|
||||
func (p *prHeap) Pop() interface{} {
|
||||
r := *p
|
||||
last := len(r) - 1
|
||||
*p = r[:last]
|
||||
return r[last]
|
||||
}
|
708
vendor/github.com/soniakeys/graph/random.go
generated
vendored
Normal file
708
vendor/github.com/soniakeys/graph/random.go
generated
vendored
Normal file
|
@ -0,0 +1,708 @@
|
|||
// Copyright 2016 Sonia Keys
|
||||
// License MIT: https://opensource.org/licenses/MIT
|
||||
|
||||
package graph
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"math"
|
||||
"math/rand"
|
||||
|
||||
"github.com/soniakeys/bits"
|
||||
)
|
||||
|
||||
// ChungLu constructs a random simple undirected graph.
|
||||
//
|
||||
// The Chung Lu model is similar to a "configuration model" where each
|
||||
// node has a specified degree. In the Chung Lu model the degree specified
|
||||
// for each node is taken as an expected degree, not an exact degree.
|
||||
//
|
||||
// Argument w is "weight," the expected degree for each node.
|
||||
// The values of w must be given in decreasing order.
|
||||
//
|
||||
// The constructed graph will have node 0 with expected degree w[0] and so on
|
||||
// so degree will decrease with node number. To randomize degree across
|
||||
// node numbers, consider using the Permute method with a rand.Perm.
|
||||
//
|
||||
// Also returned is the actual size m of constructed graph g.
|
||||
//
|
||||
// If Rand r is nil, the rand package default shared source is used.
|
||||
func ChungLu(w []float64, rr *rand.Rand) (g Undirected, m int) {
|
||||
// Ref: "Efficient Generation of Networks with Given Expected Degrees"
|
||||
// Joel C. Miller and Aric Hagberg
|
||||
// accessed at http://aric.hagberg.org/papers/miller-2011-efficient.pdf
|
||||
rf := rand.Float64
|
||||
if rr != nil {
|
||||
rf = rr.Float64
|
||||
}
|
||||
a := make(AdjacencyList, len(w))
|
||||
S := 0.
|
||||
for i := len(w) - 1; i >= 0; i-- {
|
||||
S += w[i]
|
||||
}
|
||||
for u := 0; u < len(w)-1; u++ {
|
||||
v := u + 1
|
||||
p := w[u] * w[v] / S
|
||||
if p > 1 {
|
||||
p = 1
|
||||
}
|
||||
for v < len(w) && p > 0 {
|
||||
if p != 1 {
|
||||
v += int(math.Log(rf()) / math.Log(1-p))
|
||||
}
|
||||
if v < len(w) {
|
||||
q := w[u] * w[v] / S
|
||||
if q > 1 {
|
||||
q = 1
|
||||
}
|
||||
if rf() < q/p {
|
||||
a[u] = append(a[u], NI(v))
|
||||
a[v] = append(a[v], NI(u))
|
||||
m++
|
||||
}
|
||||
p = q
|
||||
v++
|
||||
}
|
||||
}
|
||||
}
|
||||
return Undirected{a}, m
|
||||
}
|
||||
|
||||
// Euclidean generates a random simple graph on the Euclidean plane.
|
||||
//
|
||||
// Nodes are associated with coordinates uniformly distributed on a unit
|
||||
// square. Arcs are added between random nodes with a bias toward connecting
|
||||
// nearer nodes.
|
||||
//
|
||||
// Unfortunately the function has a few "knobs".
|
||||
// The returned graph will have order nNodes and arc size nArcs. The affinity
|
||||
// argument controls the bias toward connecting nearer nodes. The function
|
||||
// selects random pairs of nodes as a candidate arc then rejects the candidate
|
||||
// if the nodes fail an affinity test. Also parallel arcs are rejected.
|
||||
// As more affine or denser graphs are requested, rejections increase,
|
||||
// increasing run time. The patience argument controls the number of arc
|
||||
// rejections allowed before the function gives up and returns an error.
|
||||
// Note that higher affinity will require more patience and that some
|
||||
// combinations of nNodes and nArcs cannot be achieved with any amount of
|
||||
// patience given that the returned graph must be simple.
|
||||
//
|
||||
// If Rand r is nil, the rand package default shared source is used.
|
||||
//
|
||||
// Returned is a directed simple graph and associated positions indexed by
|
||||
// node number. In the arc list for each node, to-nodes are in random
|
||||
// order.
|
||||
//
|
||||
// See also LabeledEuclidean.
|
||||
func Euclidean(nNodes, nArcs int, affinity float64, patience int, rr *rand.Rand) (g Directed, pos []struct{ X, Y float64 }, err error) {
|
||||
a := make(AdjacencyList, nNodes) // graph
|
||||
ri, rf, re := rand.Intn, rand.Float64, rand.ExpFloat64
|
||||
if rr != nil {
|
||||
ri, rf, re = rr.Intn, rr.Float64, rr.ExpFloat64
|
||||
}
|
||||
// generate random positions
|
||||
pos = make([]struct{ X, Y float64 }, nNodes)
|
||||
for i := range pos {
|
||||
pos[i].X = rf()
|
||||
pos[i].Y = rf()
|
||||
}
|
||||
// arcs
|
||||
var tooFar, dup int
|
||||
arc:
|
||||
for i := 0; i < nArcs; {
|
||||
if tooFar == nArcs*patience {
|
||||
err = errors.New("affinity not found")
|
||||
return
|
||||
}
|
||||
if dup == nArcs*patience {
|
||||
err = errors.New("overcrowding")
|
||||
return
|
||||
}
|
||||
n1 := NI(ri(nNodes))
|
||||
var n2 NI
|
||||
for {
|
||||
n2 = NI(ri(nNodes))
|
||||
if n2 != n1 { // no graph loops
|
||||
break
|
||||
}
|
||||
}
|
||||
c1 := &pos[n1]
|
||||
c2 := &pos[n2]
|
||||
dist := math.Hypot(c2.X-c1.X, c2.Y-c1.Y)
|
||||
if dist*affinity > re() { // favor near nodes
|
||||
tooFar++
|
||||
continue
|
||||
}
|
||||
for _, nb := range a[n1] {
|
||||
if nb == n2 { // no parallel arcs
|
||||
dup++
|
||||
continue arc
|
||||
}
|
||||
}
|
||||
a[n1] = append(a[n1], n2)
|
||||
i++
|
||||
}
|
||||
g = Directed{a}
|
||||
return
|
||||
}
|
||||
|
||||
// LabeledEuclidean generates a random simple graph on the Euclidean plane.
|
||||
//
|
||||
// Arc label values in the returned graph g are indexes into the return value
|
||||
// wt. Wt is the Euclidean distance between the from and to nodes of the arc.
|
||||
//
|
||||
// Otherwise the function arguments and return values are the same as for
|
||||
// function Euclidean. See Euclidean.
|
||||
func LabeledEuclidean(nNodes, nArcs int, affinity float64, patience int, rr *rand.Rand) (g LabeledDirected, pos []struct{ X, Y float64 }, wt []float64, err error) {
|
||||
a := make(LabeledAdjacencyList, nNodes) // graph
|
||||
wt = make([]float64, nArcs) // arc weights
|
||||
ri, rf, re := rand.Intn, rand.Float64, rand.ExpFloat64
|
||||
if rr != nil {
|
||||
ri, rf, re = rr.Intn, rr.Float64, rr.ExpFloat64
|
||||
}
|
||||
// generate random positions
|
||||
pos = make([]struct{ X, Y float64 }, nNodes)
|
||||
for i := range pos {
|
||||
pos[i].X = rf()
|
||||
pos[i].Y = rf()
|
||||
}
|
||||
// arcs
|
||||
var tooFar, dup int
|
||||
arc:
|
||||
for i := 0; i < nArcs; {
|
||||
if tooFar == nArcs*patience {
|
||||
err = errors.New("affinity not found")
|
||||
return
|
||||
}
|
||||
if dup == nArcs*patience {
|
||||
err = errors.New("overcrowding")
|
||||
return
|
||||
}
|
||||
n1 := NI(ri(nNodes))
|
||||
var n2 NI
|
||||
for {
|
||||
n2 = NI(ri(nNodes))
|
||||
if n2 != n1 { // no graph loops
|
||||
break
|
||||
}
|
||||
}
|
||||
c1 := &pos[n1]
|
||||
c2 := &pos[n2]
|
||||
dist := math.Hypot(c2.X-c1.X, c2.Y-c1.Y)
|
||||
if dist*affinity > re() { // favor near nodes
|
||||
tooFar++
|
||||
continue
|
||||
}
|
||||
for _, nb := range a[n1] {
|
||||
if nb.To == n2 { // no parallel arcs
|
||||
dup++
|
||||
continue arc
|
||||
}
|
||||
}
|
||||
wt[i] = dist
|
||||
a[n1] = append(a[n1], Half{n2, LI(i)})
|
||||
i++
|
||||
}
|
||||
g = LabeledDirected{a}
|
||||
return
|
||||
}
|
||||
|
||||
// Geometric generates a random geometric graph (RGG) on the Euclidean plane.
|
||||
//
|
||||
// An RGG is an undirected simple graph. Nodes are associated with coordinates
|
||||
// uniformly distributed on a unit square. Edges are added between all nodes
|
||||
// falling within a specified distance or radius of each other.
|
||||
//
|
||||
// The resulting number of edges is somewhat random but asymptotically
|
||||
// approaches m = πr²n²/2. The method accumulates and returns the actual
|
||||
// number of edges constructed. In the arc list for each node, to-nodes are
|
||||
// ordered. Consider using ShuffleArcLists if random order is important.
|
||||
//
|
||||
// If Rand r is nil, the rand package default shared source is used.
|
||||
//
|
||||
// See also LabeledGeometric.
|
||||
func Geometric(nNodes int, radius float64, rr *rand.Rand) (g Undirected, pos []struct{ X, Y float64 }, m int) {
|
||||
// Expected degree is approximately nπr².
|
||||
a := make(AdjacencyList, nNodes)
|
||||
rf := rand.Float64
|
||||
if rr != nil {
|
||||
rf = rr.Float64
|
||||
}
|
||||
pos = make([]struct{ X, Y float64 }, nNodes)
|
||||
for i := range pos {
|
||||
pos[i].X = rf()
|
||||
pos[i].Y = rf()
|
||||
}
|
||||
for u, up := range pos {
|
||||
for v := u + 1; v < len(pos); v++ {
|
||||
vp := pos[v]
|
||||
dx := math.Abs(up.X - vp.X)
|
||||
if dx >= radius {
|
||||
continue
|
||||
}
|
||||
dy := math.Abs(up.Y - vp.Y)
|
||||
if dy >= radius {
|
||||
continue
|
||||
}
|
||||
if math.Hypot(dx, dy) < radius {
|
||||
a[u] = append(a[u], NI(v))
|
||||
a[v] = append(a[v], NI(u))
|
||||
m++
|
||||
}
|
||||
}
|
||||
}
|
||||
g = Undirected{a}
|
||||
return
|
||||
}
|
||||
|
||||
// LabeledGeometric generates a random geometric graph (RGG) on the Euclidean
|
||||
// plane.
|
||||
//
|
||||
// Edge label values in the returned graph g are indexes into the return value
|
||||
// wt. Wt is the Euclidean distance between nodes of the edge. The graph
|
||||
// size m is len(wt).
|
||||
//
|
||||
// See Geometric for additional description.
|
||||
func LabeledGeometric(nNodes int, radius float64, rr *rand.Rand) (g LabeledUndirected, pos []struct{ X, Y float64 }, wt []float64) {
|
||||
a := make(LabeledAdjacencyList, nNodes)
|
||||
rf := rand.Float64
|
||||
if rr != nil {
|
||||
rf = rr.Float64
|
||||
}
|
||||
pos = make([]struct{ X, Y float64 }, nNodes)
|
||||
for i := range pos {
|
||||
pos[i].X = rf()
|
||||
pos[i].Y = rf()
|
||||
}
|
||||
for u, up := range pos {
|
||||
for v := u + 1; v < len(pos); v++ {
|
||||
vp := pos[v]
|
||||
if w := math.Hypot(up.X-vp.X, up.Y-vp.Y); w < radius {
|
||||
a[u] = append(a[u], Half{NI(v), LI(len(wt))})
|
||||
a[v] = append(a[v], Half{NI(u), LI(len(wt))})
|
||||
wt = append(wt, w)
|
||||
}
|
||||
}
|
||||
}
|
||||
g = LabeledUndirected{a}
|
||||
return
|
||||
}
|
||||
|
||||
// GnmUndirected constructs a random simple undirected graph.
|
||||
//
|
||||
// Construction is by the Erdős–Rényi model where the specified number of
|
||||
// distinct edges is selected from all possible edges with equal probability.
|
||||
//
|
||||
// Argument n is number of nodes, m is number of edges and must be <= n(n-1)/2.
|
||||
//
|
||||
// If Rand r is nil, the rand package default shared source is used.
|
||||
//
|
||||
// In the generated arc list for each node, to-nodes are ordered.
|
||||
// Consider using ShuffleArcLists if random order is important.
|
||||
//
|
||||
// See also Gnm3Undirected, a method producing a statistically equivalent
|
||||
// result, but by an algorithm with somewhat different performance properties.
|
||||
// Performance of the two methods is expected to be similar in most cases but
|
||||
// it may be worth trying both with your data to see if one has a clear
|
||||
// advantage.
|
||||
func GnmUndirected(n, m int, rr *rand.Rand) Undirected {
|
||||
// based on Alg. 2 from "Efficient Generation of Large Random Networks",
|
||||
// Vladimir Batagelj and Ulrik Brandes.
|
||||
// accessed at http://algo.uni-konstanz.de/publications/bb-eglrn-05.pdf
|
||||
ri := rand.Intn
|
||||
if rr != nil {
|
||||
ri = rr.Intn
|
||||
}
|
||||
re := n * (n - 1) / 2
|
||||
ml := m
|
||||
if m*2 > re {
|
||||
ml = re - m
|
||||
}
|
||||
e := map[int]struct{}{}
|
||||
for len(e) < ml {
|
||||
e[ri(re)] = struct{}{}
|
||||
}
|
||||
a := make(AdjacencyList, n)
|
||||
if m*2 > re {
|
||||
i := 0
|
||||
for v := 1; v < n; v++ {
|
||||
for w := 0; w < v; w++ {
|
||||
if _, ok := e[i]; !ok {
|
||||
a[v] = append(a[v], NI(w))
|
||||
a[w] = append(a[w], NI(v))
|
||||
}
|
||||
i++
|
||||
}
|
||||
}
|
||||
} else {
|
||||
for i := range e {
|
||||
v := 1 + int(math.Sqrt(.25+float64(2*i))-.5)
|
||||
w := i - (v * (v - 1) / 2)
|
||||
a[v] = append(a[v], NI(w))
|
||||
a[w] = append(a[w], NI(v))
|
||||
}
|
||||
}
|
||||
return Undirected{a}
|
||||
}
|
||||
|
||||
// GnmDirected constructs a random simple directed graph.
|
||||
//
|
||||
// Construction is by the Erdős–Rényi model where the specified number of
|
||||
// distinct arcs is selected from all possible arcs with equal probability.
|
||||
//
|
||||
// Argument n is number of nodes, ma is number of arcs and must be <= n(n-1).
|
||||
//
|
||||
// If Rand r is nil, the rand package default shared source is used.
|
||||
//
|
||||
// In the generated arc list for each node, to-nodes are ordered.
|
||||
// Consider using ShuffleArcLists if random order is important.
|
||||
//
|
||||
// See also Gnm3Directed, a method producing a statistically equivalent
|
||||
// result, but by
|
||||
// an algorithm with somewhat different performance properties. Performance
|
||||
// of the two methods is expected to be similar in most cases but it may be
|
||||
// worth trying both with your data to see if one has a clear advantage.
|
||||
func GnmDirected(n, ma int, rr *rand.Rand) Directed {
|
||||
// based on Alg. 2 from "Efficient Generation of Large Random Networks",
|
||||
// Vladimir Batagelj and Ulrik Brandes.
|
||||
// accessed at http://algo.uni-konstanz.de/publications/bb-eglrn-05.pdf
|
||||
ri := rand.Intn
|
||||
if rr != nil {
|
||||
ri = rr.Intn
|
||||
}
|
||||
re := n * (n - 1)
|
||||
ml := ma
|
||||
if ma*2 > re {
|
||||
ml = re - ma
|
||||
}
|
||||
e := map[int]struct{}{}
|
||||
for len(e) < ml {
|
||||
e[ri(re)] = struct{}{}
|
||||
}
|
||||
a := make(AdjacencyList, n)
|
||||
if ma*2 > re {
|
||||
i := 0
|
||||
for v := 0; v < n; v++ {
|
||||
for w := 0; w < n; w++ {
|
||||
if w == v {
|
||||
continue
|
||||
}
|
||||
if _, ok := e[i]; !ok {
|
||||
a[v] = append(a[v], NI(w))
|
||||
}
|
||||
i++
|
||||
}
|
||||
}
|
||||
} else {
|
||||
for i := range e {
|
||||
v := i / (n - 1)
|
||||
w := i % (n - 1)
|
||||
if w >= v {
|
||||
w++
|
||||
}
|
||||
a[v] = append(a[v], NI(w))
|
||||
}
|
||||
}
|
||||
return Directed{a}
|
||||
}
|
||||
|
||||
// Gnm3Undirected constructs a random simple undirected graph.
|
||||
//
|
||||
// Construction is by the Erdős–Rényi model where the specified number of
|
||||
// distinct edges is selected from all possible edges with equal probability.
|
||||
//
|
||||
// Argument n is number of nodes, m is number of edges and must be <= n(n-1)/2.
|
||||
//
|
||||
// If Rand r is nil, the rand package default shared source is used.
|
||||
//
|
||||
// In the generated arc list for each node, to-nodes are ordered.
|
||||
// Consider using ShuffleArcLists if random order is important.
|
||||
//
|
||||
// See also GnmUndirected, a method producing a statistically equivalent
|
||||
// result, but by an algorithm with somewhat different performance properties.
|
||||
// Performance of the two methods is expected to be similar in most cases but
|
||||
// it may be worth trying both with your data to see if one has a clear
|
||||
// advantage.
|
||||
func Gnm3Undirected(n, m int, rr *rand.Rand) Undirected {
|
||||
// based on Alg. 3 from "Efficient Generation of Large Random Networks",
|
||||
// Vladimir Batagelj and Ulrik Brandes.
|
||||
// accessed at http://algo.uni-konstanz.de/publications/bb-eglrn-05.pdf
|
||||
//
|
||||
// I like this algorithm for its elegance. Pitty it tends to run a
|
||||
// a little slower than the retry algorithm of Gnm.
|
||||
ri := rand.Intn
|
||||
if rr != nil {
|
||||
ri = rr.Intn
|
||||
}
|
||||
a := make(AdjacencyList, n)
|
||||
re := n * (n - 1) / 2
|
||||
rm := map[int]int{}
|
||||
for i := 0; i < m; i++ {
|
||||
er := i + ri(re-i)
|
||||
eNew := er
|
||||
if rp, ok := rm[er]; ok {
|
||||
eNew = rp
|
||||
}
|
||||
if rp, ok := rm[i]; !ok {
|
||||
rm[er] = i
|
||||
} else {
|
||||
rm[er] = rp
|
||||
}
|
||||
v := 1 + int(math.Sqrt(.25+float64(2*eNew))-.5)
|
||||
w := eNew - (v * (v - 1) / 2)
|
||||
a[v] = append(a[v], NI(w))
|
||||
a[w] = append(a[w], NI(v))
|
||||
}
|
||||
return Undirected{a}
|
||||
}
|
||||
|
||||
// Gnm3Directed constructs a random simple directed graph.
|
||||
//
|
||||
// Construction is by the Erdős–Rényi model where the specified number of
|
||||
// distinct arcs is selected from all possible arcs with equal probability.
|
||||
//
|
||||
// Argument n is number of nodes, ma is number of arcs and must be <= n(n-1).
|
||||
//
|
||||
// If Rand r is nil, the rand package default shared source is used.
|
||||
//
|
||||
// In the generated arc list for each node, to-nodes are ordered.
|
||||
// Consider using ShuffleArcLists if random order is important.
|
||||
//
|
||||
// See also GnmDirected, a method producing a statistically equivalent result,
|
||||
// but by an algorithm with somewhat different performance properties.
|
||||
// Performance of the two methods is expected to be similar in most cases
|
||||
// but it may be worth trying both with your data to see if one has a clear
|
||||
// advantage.
|
||||
func Gnm3Directed(n, ma int, rr *rand.Rand) Directed {
|
||||
// based on Alg. 3 from "Efficient Generation of Large Random Networks",
|
||||
// Vladimir Batagelj and Ulrik Brandes.
|
||||
// accessed at http://algo.uni-konstanz.de/publications/bb-eglrn-05.pdf
|
||||
ri := rand.Intn
|
||||
if rr != nil {
|
||||
ri = rr.Intn
|
||||
}
|
||||
a := make(AdjacencyList, n)
|
||||
re := n * (n - 1)
|
||||
rm := map[int]int{}
|
||||
for i := 0; i < ma; i++ {
|
||||
er := i + ri(re-i)
|
||||
eNew := er
|
||||
if rp, ok := rm[er]; ok {
|
||||
eNew = rp
|
||||
}
|
||||
if rp, ok := rm[i]; !ok {
|
||||
rm[er] = i
|
||||
} else {
|
||||
rm[er] = rp
|
||||
}
|
||||
v := eNew / (n - 1)
|
||||
w := eNew % (n - 1)
|
||||
if w >= v {
|
||||
w++
|
||||
}
|
||||
a[v] = append(a[v], NI(w))
|
||||
}
|
||||
return Directed{a}
|
||||
}
|
||||
|
||||
// GnpUndirected constructs a random simple undirected graph.
|
||||
//
|
||||
// Construction is by the Gilbert model, an Erdős–Rényi like model where
|
||||
// distinct edges are independently selected from all possible edges with
|
||||
// the specified probability.
|
||||
//
|
||||
// Argument n is number of nodes, p is probability for selecting an edge.
|
||||
//
|
||||
// If Rand r is nil, the rand package default shared source is used.
|
||||
//
|
||||
// In the generated arc list for each node, to-nodes are ordered.
|
||||
// Consider using ShuffleArcLists if random order is important.
|
||||
//
|
||||
// Also returned is the actual size m of constructed graph g.
|
||||
func GnpUndirected(n int, p float64, rr *rand.Rand) (g Undirected, m int) {
|
||||
a := make(AdjacencyList, n)
|
||||
if n < 2 {
|
||||
return Undirected{a}, 0
|
||||
}
|
||||
rf := rand.Float64
|
||||
if rr != nil {
|
||||
rf = rr.Float64
|
||||
}
|
||||
// based on Alg. 1 from "Efficient Generation of Large Random Networks",
|
||||
// Vladimir Batagelj and Ulrik Brandes.
|
||||
// accessed at http://algo.uni-konstanz.de/publications/bb-eglrn-05.pdf
|
||||
var v, w NI = 1, -1
|
||||
g:
|
||||
for c := 1 / math.Log(1-p); ; {
|
||||
w += 1 + NI(c*math.Log(1-rf()))
|
||||
for {
|
||||
if w < v {
|
||||
a[v] = append(a[v], w)
|
||||
a[w] = append(a[w], v)
|
||||
m++
|
||||
continue g
|
||||
}
|
||||
w -= v
|
||||
v++
|
||||
if v == NI(n) {
|
||||
break g
|
||||
}
|
||||
}
|
||||
}
|
||||
return Undirected{a}, m
|
||||
}
|
||||
|
||||
// GnpDirected constructs a random simple directed graph.
|
||||
//
|
||||
// Construction is by the Gilbert model, an Erdős–Rényi like model where
|
||||
// distinct arcs are independently selected from all possible arcs with
|
||||
// the specified probability.
|
||||
//
|
||||
// Argument n is number of nodes, p is probability for selecting an arc.
|
||||
//
|
||||
// If Rand r is nil, the rand package default shared source is used.
|
||||
//
|
||||
// In the generated arc list for each node, to-nodes are ordered.
|
||||
// Consider using ShuffleArcLists if random order is important.
|
||||
//
|
||||
// Also returned is the actual arc size m of constructed graph g.
|
||||
func GnpDirected(n int, p float64, rr *rand.Rand) (g Directed, ma int) {
|
||||
a := make(AdjacencyList, n)
|
||||
if n < 2 {
|
||||
return Directed{a}, 0
|
||||
}
|
||||
rf := rand.Float64
|
||||
if rr != nil {
|
||||
rf = rr.Float64
|
||||
}
|
||||
// based on Alg. 1 from "Efficient Generation of Large Random Networks",
|
||||
// Vladimir Batagelj and Ulrik Brandes.
|
||||
// accessed at http://algo.uni-konstanz.de/publications/bb-eglrn-05.pdf
|
||||
var v, w NI = 0, -1
|
||||
g:
|
||||
for c := 1 / math.Log(1-p); ; {
|
||||
w += 1 + NI(c*math.Log(1-rf()))
|
||||
for ; ; w -= NI(n) {
|
||||
if w == v {
|
||||
w++
|
||||
}
|
||||
if w < NI(n) {
|
||||
a[v] = append(a[v], w)
|
||||
ma++
|
||||
continue g
|
||||
}
|
||||
v++
|
||||
if v == NI(n) {
|
||||
break g
|
||||
}
|
||||
}
|
||||
}
|
||||
return Directed{a}, ma
|
||||
}
|
||||
|
||||
// KroneckerDirected generates a Kronecker-like random directed graph.
|
||||
//
|
||||
// The returned graph g is simple and has no isolated nodes but is not
|
||||
// necessarily fully connected. The number of of nodes will be <= 2^scale,
|
||||
// and will be near 2^scale for typical values of arcFactor, >= 2.
|
||||
// ArcFactor * 2^scale arcs are generated, although loops and duplicate arcs
|
||||
// are rejected. In the arc list for each node, to-nodes are in random
|
||||
// order.
|
||||
//
|
||||
// If Rand r is nil, the rand package default shared source is used.
|
||||
//
|
||||
// Return value ma is the number of arcs retained in the result graph.
|
||||
func KroneckerDirected(scale uint, arcFactor float64, rr *rand.Rand) (g Directed, ma int) {
|
||||
a, m := kronecker(scale, arcFactor, true, rr)
|
||||
return Directed{a}, m
|
||||
}
|
||||
|
||||
// KroneckerUndirected generates a Kronecker-like random undirected graph.
|
||||
//
|
||||
// The returned graph g is simple and has no isolated nodes but is not
|
||||
// necessarily fully connected. The number of of nodes will be <= 2^scale,
|
||||
// and will be near 2^scale for typical values of edgeFactor, >= 2.
|
||||
// EdgeFactor * 2^scale edges are generated, although loops and duplicate edges
|
||||
// are rejected. In the arc list for each node, to-nodes are in random
|
||||
// order.
|
||||
//
|
||||
// If Rand r is nil, the rand package default shared source is used.
|
||||
//
|
||||
// Return value m is the true number of edges--not arcs--retained in the result
|
||||
// graph.
|
||||
func KroneckerUndirected(scale uint, edgeFactor float64, rr *rand.Rand) (g Undirected, m int) {
|
||||
al, s := kronecker(scale, edgeFactor, false, rr)
|
||||
return Undirected{al}, s
|
||||
}
|
||||
|
||||
// Styled after the Graph500 example code. Not well tested currently.
|
||||
// Graph500 example generates undirected only. No idea if the directed variant
|
||||
// here is meaningful or not.
|
||||
//
|
||||
// note mma returns arc size ma for dir=true, but returns size m for dir=false
|
||||
func kronecker(scale uint, edgeFactor float64, dir bool, rr *rand.Rand) (g AdjacencyList, mma int) {
|
||||
rf, ri, rp := rand.Float64, rand.Intn, rand.Perm
|
||||
if rr != nil {
|
||||
rf, ri, rp = rr.Float64, rr.Intn, rr.Perm
|
||||
}
|
||||
N := 1 << scale // node extent
|
||||
M := int(edgeFactor*float64(N) + .5) // number of arcs/edges to generate
|
||||
a, b, c := 0.57, 0.19, 0.19 // initiator probabilities
|
||||
ab := a + b
|
||||
cNorm := c / (1 - ab)
|
||||
aNorm := a / ab
|
||||
ij := make([][2]NI, M)
|
||||
bm := bits.New(N)
|
||||
var nNodes int
|
||||
for k := range ij {
|
||||
var i, j int
|
||||
for b := 1; b < N; b <<= 1 {
|
||||
if rf() > ab {
|
||||
i |= b
|
||||
if rf() > cNorm {
|
||||
j |= b
|
||||
}
|
||||
} else if rf() > aNorm {
|
||||
j |= b
|
||||
}
|
||||
}
|
||||
if bm.Bit(i) == 0 {
|
||||
bm.SetBit(i, 1)
|
||||
nNodes++
|
||||
}
|
||||
if bm.Bit(j) == 0 {
|
||||
bm.SetBit(j, 1)
|
||||
nNodes++
|
||||
}
|
||||
r := ri(k + 1) // shuffle edges as they are generated
|
||||
ij[k] = ij[r]
|
||||
ij[r] = [2]NI{NI(i), NI(j)}
|
||||
}
|
||||
p := rp(nNodes) // mapping to shuffle IDs of non-isolated nodes
|
||||
px := 0
|
||||
rn := make([]NI, N)
|
||||
for i := range rn {
|
||||
if bm.Bit(i) == 1 {
|
||||
rn[i] = NI(p[px]) // fill lookup table
|
||||
px++
|
||||
}
|
||||
}
|
||||
g = make(AdjacencyList, nNodes)
|
||||
ij:
|
||||
for _, e := range ij {
|
||||
if e[0] == e[1] {
|
||||
continue // skip loops
|
||||
}
|
||||
ri, rj := rn[e[0]], rn[e[1]]
|
||||
for _, nb := range g[ri] {
|
||||
if nb == rj {
|
||||
continue ij // skip parallel edges
|
||||
}
|
||||
}
|
||||
g[ri] = append(g[ri], rj)
|
||||
mma++
|
||||
if !dir {
|
||||
g[rj] = append(g[rj], ri)
|
||||
}
|
||||
}
|
||||
return
|
||||
}
|
50
vendor/github.com/soniakeys/graph/readme.adoc
generated
vendored
Normal file
50
vendor/github.com/soniakeys/graph/readme.adoc
generated
vendored
Normal file
|
@ -0,0 +1,50 @@
|
|||
= Graph
|
||||
|
||||
A graph library with goals of speed and simplicity, Graph implements
|
||||
graph algorithms on graphs of zero-based integer node IDs.
|
||||
|
||||
image:https://godoc.org/github.com/soniakeys/graph?status.svg[link=https://godoc.org/github.com/soniakeys/graph]
|
||||
image:http://gowalker.org/api/v1/badge[link=https://gowalker.org/github.com/soniakeys/graph]
|
||||
image:http://go-search.org/badge?id=github.com%2Fsoniakeys%2Fgraph[link=http://go-search.org/view?id=github.com%2Fsoniakeys%2Fgraph]
|
||||
image:https://travis-ci.org/soniakeys/graph.svg?branch=master[link=https://travis-ci.org/soniakeys/graph]
|
||||
|
||||
The library provides efficient graph representations and many methods on
|
||||
graph types. It can be imported and used directly in many applications that
|
||||
require or can benefit from graph algorithms.
|
||||
|
||||
The library should also be considered as library of source code that can serve
|
||||
as starting material for coding variant or more complex algorithms.
|
||||
|
||||
== Ancillary material of interest
|
||||
|
||||
The directory link:tutorials[tutorials] is a work in progress - there are only
|
||||
a few tutorials there yet - but the concept is to provide some topical
|
||||
walk-throughs to supplement godoc. The source-based godoc documentation
|
||||
remains the primary documentation.
|
||||
|
||||
The directory link:anecdote[anecdote] contains a stand-alone program that
|
||||
performs single runs of a number of methods, collecting one-off or "anecdotal"
|
||||
timings. It currently runs only a small fraction of the library methods but
|
||||
may still be of interest for giving a general idea of how fast some methods
|
||||
run.
|
||||
|
||||
The directory link:bench[bench] is another work in progress. The concept is
|
||||
to present some plots showing benchmark performance approaching some
|
||||
theoretical asymptote.
|
||||
|
||||
link:hacking.adoc[hacking.adoc] has some information about how the library is
|
||||
developed, built, and tested. It might be of interest if for example you
|
||||
plan to fork or contribute to the the repository.
|
||||
|
||||
== Test coverage
|
||||
1 Jul 2017
|
||||
....
|
||||
graph 93.7%
|
||||
graph/alt 88.0%
|
||||
graph/dot 77.7%
|
||||
graph/treevis 79.4%
|
||||
....
|
||||
|
||||
== License
|
||||
All files in the repository are licensed with the MIT License,
|
||||
https://opensource.org/licenses/MIT.
|
761
vendor/github.com/soniakeys/graph/sssp.go
generated
vendored
Normal file
761
vendor/github.com/soniakeys/graph/sssp.go
generated
vendored
Normal file
|
@ -0,0 +1,761 @@
|
|||
// Copyright 2013 Sonia Keys
|
||||
// License MIT: http://opensource.org/licenses/MIT
|
||||
|
||||
package graph
|
||||
|
||||
import (
|
||||
"container/heap"
|
||||
"fmt"
|
||||
"math"
|
||||
|
||||
"github.com/soniakeys/bits"
|
||||
)
|
||||
|
||||
// rNode holds data for a "reached" node
|
||||
type rNode struct {
|
||||
nx NI
|
||||
state int8 // state constants defined below
|
||||
f float64 // "g+h", path dist + heuristic estimate
|
||||
fx int // heap.Fix index
|
||||
}
|
||||
|
||||
// for rNode.state
|
||||
const (
|
||||
unreached = 0
|
||||
reached = 1
|
||||
open = 1
|
||||
closed = 2
|
||||
)
|
||||
|
||||
type openHeap []*rNode
|
||||
|
||||
// A Heuristic is defined on a specific end node. The function
|
||||
// returns an estimate of the path distance from node argument
|
||||
// "from" to the end node. Two subclasses of heuristics are "admissible"
|
||||
// and "monotonic."
|
||||
//
|
||||
// Admissible means the value returned is guaranteed to be less than or
|
||||
// equal to the actual shortest path distance from the node to end.
|
||||
//
|
||||
// An admissible estimate may further be monotonic.
|
||||
// Monotonic means that for any neighboring nodes A and B with half arc aB
|
||||
// leading from A to B, and for heuristic h defined on some end node, then
|
||||
// h(A) <= aB.ArcWeight + h(B).
|
||||
//
|
||||
// See AStarA for additional notes on implementing heuristic functions for
|
||||
// AStar search methods.
|
||||
type Heuristic func(from NI) float64
|
||||
|
||||
// Admissible returns true if heuristic h is admissible on graph g relative to
|
||||
// the given end node.
|
||||
//
|
||||
// If h is inadmissible, the string result describes a counter example.
|
||||
func (h Heuristic) Admissible(g LabeledAdjacencyList, w WeightFunc, end NI) (bool, string) {
|
||||
// invert graph
|
||||
inv := make(LabeledAdjacencyList, len(g))
|
||||
for from, nbs := range g {
|
||||
for _, nb := range nbs {
|
||||
inv[nb.To] = append(inv[nb.To],
|
||||
Half{To: NI(from), Label: nb.Label})
|
||||
}
|
||||
}
|
||||
// run dijkstra
|
||||
// Dijkstra.AllPaths takes a start node but after inverting the graph
|
||||
// argument end now represents the start node of the inverted graph.
|
||||
f, _, dist, _ := inv.Dijkstra(end, -1, w)
|
||||
// compare h to found shortest paths
|
||||
for n := range inv {
|
||||
if f.Paths[n].Len == 0 {
|
||||
continue // no path, any heuristic estimate is fine.
|
||||
}
|
||||
if !(h(NI(n)) <= dist[n]) {
|
||||
return false, fmt.Sprintf("h(%d) = %g, "+
|
||||
"required to be <= found shortest path (%g)",
|
||||
n, h(NI(n)), dist[n])
|
||||
}
|
||||
}
|
||||
return true, ""
|
||||
}
|
||||
|
||||
// Monotonic returns true if heuristic h is monotonic on weighted graph g.
|
||||
//
|
||||
// If h is non-monotonic, the string result describes a counter example.
|
||||
func (h Heuristic) Monotonic(g LabeledAdjacencyList, w WeightFunc) (bool, string) {
|
||||
// precompute
|
||||
hv := make([]float64, len(g))
|
||||
for n := range g {
|
||||
hv[n] = h(NI(n))
|
||||
}
|
||||
// iterate over all edges
|
||||
for from, nbs := range g {
|
||||
for _, nb := range nbs {
|
||||
arcWeight := w(nb.Label)
|
||||
if !(hv[from] <= arcWeight+hv[nb.To]) {
|
||||
return false, fmt.Sprintf("h(%d) = %g, "+
|
||||
"required to be <= arc weight + h(%d) (= %g + %g = %g)",
|
||||
from, hv[from],
|
||||
nb.To, arcWeight, hv[nb.To], arcWeight+hv[nb.To])
|
||||
}
|
||||
}
|
||||
}
|
||||
return true, ""
|
||||
}
|
||||
|
||||
// AStarA finds a path between two nodes.
|
||||
//
|
||||
// AStarA implements both algorithm A and algorithm A*. The difference in the
|
||||
// two algorithms is strictly in the heuristic estimate returned by argument h.
|
||||
// If h is an "admissible" heuristic estimate, then the algorithm is termed A*,
|
||||
// otherwise it is algorithm A.
|
||||
//
|
||||
// Like Dijkstra's algorithm, AStarA with an admissible heuristic finds the
|
||||
// shortest path between start and end. AStarA generally runs faster than
|
||||
// Dijkstra though, by using the heuristic distance estimate.
|
||||
//
|
||||
// AStarA with an inadmissible heuristic becomes algorithm A. Algorithm A
|
||||
// will find a path, but it is not guaranteed to be the shortest path.
|
||||
// The heuristic still guides the search however, so a nearly admissible
|
||||
// heuristic is likely to find a very good path, if not the best. Quality
|
||||
// of the path returned degrades gracefully with the quality of the heuristic.
|
||||
//
|
||||
// The heuristic function h should ideally be fairly inexpensive. AStarA
|
||||
// may call it more than once for the same node, especially as graph density
|
||||
// increases. In some cases it may be worth the effort to memoize or
|
||||
// precompute values.
|
||||
//
|
||||
// Argument g is the graph to be searched, with arc weights returned by w.
|
||||
// As usual for AStar, arc weights must be non-negative.
|
||||
// Graphs may be directed or undirected.
|
||||
//
|
||||
// If AStarA finds a path it returns a FromList encoding the path, the arc
|
||||
// labels for path nodes, the total path distance, and ok = true.
|
||||
// Otherwise it returns ok = false.
|
||||
func (g LabeledAdjacencyList) AStarA(w WeightFunc, start, end NI, h Heuristic) (f FromList, labels []LI, dist float64, ok bool) {
|
||||
// NOTE: AStarM is largely duplicate code.
|
||||
|
||||
f = NewFromList(len(g))
|
||||
labels = make([]LI, len(g))
|
||||
d := make([]float64, len(g))
|
||||
r := make([]rNode, len(g))
|
||||
for i := range r {
|
||||
r[i].nx = NI(i)
|
||||
}
|
||||
// start node is reached initially
|
||||
cr := &r[start]
|
||||
cr.state = reached
|
||||
cr.f = h(start) // total path estimate is estimate from start
|
||||
rp := f.Paths
|
||||
rp[start] = PathEnd{Len: 1, From: -1} // path length at start is 1 node
|
||||
// oh is a heap of nodes "open" for exploration. nodes go on the heap
|
||||
// when they get an initial or new "g" path distance, and therefore a
|
||||
// new "f" which serves as priority for exploration.
|
||||
oh := openHeap{cr}
|
||||
for len(oh) > 0 {
|
||||
bestPath := heap.Pop(&oh).(*rNode)
|
||||
bestNode := bestPath.nx
|
||||
if bestNode == end {
|
||||
return f, labels, d[end], true
|
||||
}
|
||||
bp := &rp[bestNode]
|
||||
nextLen := bp.Len + 1
|
||||
for _, nb := range g[bestNode] {
|
||||
alt := &r[nb.To]
|
||||
ap := &rp[alt.nx]
|
||||
// "g" path distance from start
|
||||
g := d[bestNode] + w(nb.Label)
|
||||
if alt.state == reached {
|
||||
if g > d[nb.To] {
|
||||
// candidate path to nb is longer than some alternate path
|
||||
continue
|
||||
}
|
||||
if g == d[nb.To] && nextLen >= ap.Len {
|
||||
// candidate path has identical length of some alternate
|
||||
// path but it takes no fewer hops.
|
||||
continue
|
||||
}
|
||||
// cool, we found a better way to get to this node.
|
||||
// record new path data for this node and
|
||||
// update alt with new data and make sure it's on the heap.
|
||||
*ap = PathEnd{From: bestNode, Len: nextLen}
|
||||
labels[nb.To] = nb.Label
|
||||
d[nb.To] = g
|
||||
alt.f = g + h(nb.To)
|
||||
if alt.fx < 0 {
|
||||
heap.Push(&oh, alt)
|
||||
} else {
|
||||
heap.Fix(&oh, alt.fx)
|
||||
}
|
||||
} else {
|
||||
// bestNode being reached for the first time.
|
||||
*ap = PathEnd{From: bestNode, Len: nextLen}
|
||||
labels[nb.To] = nb.Label
|
||||
d[nb.To] = g
|
||||
alt.f = g + h(nb.To)
|
||||
alt.state = reached
|
||||
heap.Push(&oh, alt) // and it's now open for exploration
|
||||
}
|
||||
}
|
||||
}
|
||||
return // no path
|
||||
}
|
||||
|
||||
// AStarAPath finds a shortest path using the AStarA algorithm.
|
||||
//
|
||||
// This is a convenience method with a simpler result than the AStarA method.
|
||||
// See documentation on the AStarA method.
|
||||
//
|
||||
// If a path is found, the non-nil node path is returned with the total path
|
||||
// distance. Otherwise the returned path will be nil.
|
||||
func (g LabeledAdjacencyList) AStarAPath(start, end NI, h Heuristic, w WeightFunc) (LabeledPath, float64) {
|
||||
f, labels, d, _ := g.AStarA(w, start, end, h)
|
||||
return f.PathToLabeled(end, labels, nil), d
|
||||
}
|
||||
|
||||
// AStarM is AStarA optimized for monotonic heuristic estimates.
|
||||
//
|
||||
// Note that this function requires a monotonic heuristic. Results will
|
||||
// not be meaningful if argument h is non-monotonic.
|
||||
//
|
||||
// See AStarA for general usage. See Heuristic for notes on monotonicity.
|
||||
func (g LabeledAdjacencyList) AStarM(w WeightFunc, start, end NI, h Heuristic) (f FromList, labels []LI, dist float64, ok bool) {
|
||||
// NOTE: AStarM is largely code duplicated from AStarA.
|
||||
// Differences are noted in comments in this method.
|
||||
|
||||
f = NewFromList(len(g))
|
||||
labels = make([]LI, len(g))
|
||||
d := make([]float64, len(g))
|
||||
r := make([]rNode, len(g))
|
||||
for i := range r {
|
||||
r[i].nx = NI(i)
|
||||
}
|
||||
cr := &r[start]
|
||||
|
||||
// difference from AStarA:
|
||||
// instead of a bit to mark a reached node, there are two states,
|
||||
// open and closed. open marks nodes "open" for exploration.
|
||||
// nodes are marked open as they are reached, then marked
|
||||
// closed as they are found to be on the best path.
|
||||
cr.state = open
|
||||
|
||||
cr.f = h(start)
|
||||
rp := f.Paths
|
||||
rp[start] = PathEnd{Len: 1, From: -1}
|
||||
oh := openHeap{cr}
|
||||
for len(oh) > 0 {
|
||||
bestPath := heap.Pop(&oh).(*rNode)
|
||||
bestNode := bestPath.nx
|
||||
if bestNode == end {
|
||||
return f, labels, d[end], true
|
||||
}
|
||||
|
||||
// difference from AStarA:
|
||||
// move nodes to closed list as they are found to be best so far.
|
||||
bestPath.state = closed
|
||||
|
||||
bp := &rp[bestNode]
|
||||
nextLen := bp.Len + 1
|
||||
for _, nb := range g[bestNode] {
|
||||
alt := &r[nb.To]
|
||||
|
||||
// difference from AStarA:
|
||||
// Monotonicity means that f cannot be improved.
|
||||
if alt.state == closed {
|
||||
continue
|
||||
}
|
||||
|
||||
ap := &rp[alt.nx]
|
||||
g := d[bestNode] + w(nb.Label)
|
||||
|
||||
// difference from AStarA:
|
||||
// test for open state, not just reached
|
||||
if alt.state == open {
|
||||
|
||||
if g > d[nb.To] {
|
||||
continue
|
||||
}
|
||||
if g == d[nb.To] && nextLen >= ap.Len {
|
||||
continue
|
||||
}
|
||||
*ap = PathEnd{From: bestNode, Len: nextLen}
|
||||
labels[nb.To] = nb.Label
|
||||
d[nb.To] = g
|
||||
alt.f = g + h(nb.To)
|
||||
|
||||
// difference from AStarA:
|
||||
// we know alt was on the heap because we found it marked open
|
||||
heap.Fix(&oh, alt.fx)
|
||||
} else {
|
||||
*ap = PathEnd{From: bestNode, Len: nextLen}
|
||||
labels[nb.To] = nb.Label
|
||||
d[nb.To] = g
|
||||
alt.f = g + h(nb.To)
|
||||
|
||||
// difference from AStarA:
|
||||
// nodes are opened when first reached
|
||||
alt.state = open
|
||||
heap.Push(&oh, alt)
|
||||
}
|
||||
}
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
// AStarMPath finds a shortest path using the AStarM algorithm.
|
||||
//
|
||||
// This is a convenience method with a simpler result than the AStarM method.
|
||||
// See documentation on the AStarM and AStarA methods.
|
||||
//
|
||||
// If a path is found, the non-nil node path is returned with the total path
|
||||
// distance. Otherwise the returned path will be nil.
|
||||
func (g LabeledAdjacencyList) AStarMPath(start, end NI, h Heuristic, w WeightFunc) (LabeledPath, float64) {
|
||||
f, labels, d, _ := g.AStarM(w, start, end, h)
|
||||
return f.PathToLabeled(end, labels, nil), d
|
||||
}
|
||||
|
||||
// implement container/heap
|
||||
func (h openHeap) Len() int { return len(h) }
|
||||
func (h openHeap) Less(i, j int) bool { return h[i].f < h[j].f }
|
||||
func (h openHeap) Swap(i, j int) {
|
||||
h[i], h[j] = h[j], h[i]
|
||||
h[i].fx = i
|
||||
h[j].fx = j
|
||||
}
|
||||
func (p *openHeap) Push(x interface{}) {
|
||||
h := *p
|
||||
fx := len(h)
|
||||
h = append(h, x.(*rNode))
|
||||
h[fx].fx = fx
|
||||
*p = h
|
||||
}
|
||||
|
||||
func (p *openHeap) Pop() interface{} {
|
||||
h := *p
|
||||
last := len(h) - 1
|
||||
*p = h[:last]
|
||||
h[last].fx = -1
|
||||
return h[last]
|
||||
}
|
||||
|
||||
// BellmanFord finds shortest paths from a start node in a weighted directed
|
||||
// graph using the Bellman-Ford-Moore algorithm.
|
||||
//
|
||||
// WeightFunc w must translate arc labels to arc weights.
|
||||
// Negative arc weights are allowed but not negative cycles.
|
||||
// Loops and parallel arcs are allowed.
|
||||
//
|
||||
// If the algorithm completes without encountering a negative cycle the method
|
||||
// returns shortest paths encoded in a FromList, labels and path distances
|
||||
// indexed by node, and return value end = -1.
|
||||
//
|
||||
// If it encounters a negative cycle reachable from start it returns end >= 0.
|
||||
// In this case the cycle can be obtained by calling f.BellmanFordCycle(end).
|
||||
//
|
||||
// Negative cycles are only detected when reachable from start. A negative
|
||||
// cycle not reachable from start will not prevent the algorithm from finding
|
||||
// shortest paths from start.
|
||||
//
|
||||
// See also NegativeCycle to find a cycle anywhere in the graph, see
|
||||
// NegativeCycles for enumerating all negative cycles, and see
|
||||
// HasNegativeCycle for lighter-weight negative cycle detection,
|
||||
func (g LabeledDirected) BellmanFord(w WeightFunc, start NI) (f FromList, labels []LI, dist []float64, end NI) {
|
||||
a := g.LabeledAdjacencyList
|
||||
f = NewFromList(len(a))
|
||||
labels = make([]LI, len(a))
|
||||
dist = make([]float64, len(a))
|
||||
inf := math.Inf(1)
|
||||
for i := range dist {
|
||||
dist[i] = inf
|
||||
}
|
||||
rp := f.Paths
|
||||
rp[start] = PathEnd{Len: 1, From: -1}
|
||||
dist[start] = 0
|
||||
for _ = range a[1:] {
|
||||
imp := false
|
||||
for from, nbs := range a {
|
||||
fp := &rp[from]
|
||||
d1 := dist[from]
|
||||
for _, nb := range nbs {
|
||||
d2 := d1 + w(nb.Label)
|
||||
to := &rp[nb.To]
|
||||
// TODO improve to break ties
|
||||
if fp.Len > 0 && d2 < dist[nb.To] {
|
||||
*to = PathEnd{From: NI(from), Len: fp.Len + 1}
|
||||
labels[nb.To] = nb.Label
|
||||
dist[nb.To] = d2
|
||||
imp = true
|
||||
}
|
||||
}
|
||||
}
|
||||
if !imp {
|
||||
break
|
||||
}
|
||||
}
|
||||
for from, nbs := range a {
|
||||
d1 := dist[from]
|
||||
for _, nb := range nbs {
|
||||
if d1+w(nb.Label) < dist[nb.To] {
|
||||
// return nb as end of a path with negative cycle at root
|
||||
return f, labels, dist, NI(from)
|
||||
}
|
||||
}
|
||||
}
|
||||
return f, labels, dist, -1
|
||||
}
|
||||
|
||||
// BellmanFordCycle decodes a negative cycle detected by BellmanFord.
|
||||
//
|
||||
// Receiver f and argument end must be results returned from BellmanFord.
|
||||
func (f FromList) BellmanFordCycle(end NI) (c []NI) {
|
||||
p := f.Paths
|
||||
b := bits.New(len(p))
|
||||
for b.Bit(int(end)) == 0 {
|
||||
b.SetBit(int(end), 1)
|
||||
end = p[end].From
|
||||
}
|
||||
for b.Bit(int(end)) == 1 {
|
||||
c = append(c, end)
|
||||
b.SetBit(int(end), 0)
|
||||
end = p[end].From
|
||||
}
|
||||
for i, j := 0, len(c)-1; i < j; i, j = i+1, j-1 {
|
||||
c[i], c[j] = c[j], c[i]
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
// HasNegativeCycle returns true if the graph contains any negative cycle.
|
||||
//
|
||||
// HasNegativeCycle uses a Bellman-Ford-like algorithm, but finds negative
|
||||
// cycles anywhere in the graph. Also path information is not computed,
|
||||
// reducing memory use somewhat compared to BellmanFord.
|
||||
//
|
||||
// See also NegativeCycle to obtain the cycle, see NegativeCycles for
|
||||
// enumerating all negative cycles, and see BellmanFord for single source
|
||||
// shortest path searches with negative cycle detection.
|
||||
func (g LabeledDirected) HasNegativeCycle(w WeightFunc) bool {
|
||||
a := g.LabeledAdjacencyList
|
||||
dist := make([]float64, len(a))
|
||||
for _ = range a[1:] {
|
||||
imp := false
|
||||
for from, nbs := range a {
|
||||
d1 := dist[from]
|
||||
for _, nb := range nbs {
|
||||
d2 := d1 + w(nb.Label)
|
||||
if d2 < dist[nb.To] {
|
||||
dist[nb.To] = d2
|
||||
imp = true
|
||||
}
|
||||
}
|
||||
}
|
||||
if !imp {
|
||||
break
|
||||
}
|
||||
}
|
||||
for from, nbs := range a {
|
||||
d1 := dist[from]
|
||||
for _, nb := range nbs {
|
||||
if d1+w(nb.Label) < dist[nb.To] {
|
||||
return true // negative cycle
|
||||
}
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
// NegativeCycle finds a negative cycle if one exists.
|
||||
//
|
||||
// NegativeCycle uses a Bellman-Ford-like algorithm, but finds negative
|
||||
// cycles anywhere in the graph. If a negative cycle exists, one will be
|
||||
// returned. The result is nil if no negative cycle exists.
|
||||
//
|
||||
// See also NegativeCycles for enumerating all negative cycles, see
|
||||
// HasNegativeCycle for lighter-weight cycle detection, and see
|
||||
// BellmanFord for single source shortest paths, also with negative cycle
|
||||
// detection.
|
||||
func (g LabeledDirected) NegativeCycle(w WeightFunc) (c []Half) {
|
||||
a := g.LabeledAdjacencyList
|
||||
f := NewFromList(len(a))
|
||||
p := f.Paths
|
||||
for n := range p {
|
||||
p[n] = PathEnd{From: -1, Len: 1}
|
||||
}
|
||||
labels := make([]LI, len(a))
|
||||
dist := make([]float64, len(a))
|
||||
for _ = range a {
|
||||
imp := false
|
||||
for from, nbs := range a {
|
||||
fp := &p[from]
|
||||
d1 := dist[from]
|
||||
for _, nb := range nbs {
|
||||
d2 := d1 + w(nb.Label)
|
||||
to := &p[nb.To]
|
||||
if fp.Len > 0 && d2 < dist[nb.To] {
|
||||
*to = PathEnd{From: NI(from), Len: fp.Len + 1}
|
||||
labels[nb.To] = nb.Label
|
||||
dist[nb.To] = d2
|
||||
imp = true
|
||||
}
|
||||
}
|
||||
}
|
||||
if !imp {
|
||||
return nil
|
||||
}
|
||||
}
|
||||
vis := bits.New(len(a))
|
||||
a:
|
||||
for n := range a {
|
||||
end := n
|
||||
b := bits.New(len(a))
|
||||
for b.Bit(end) == 0 {
|
||||
if vis.Bit(end) == 1 {
|
||||
continue a
|
||||
}
|
||||
vis.SetBit(end, 1)
|
||||
b.SetBit(end, 1)
|
||||
end = int(p[end].From)
|
||||
if end < 0 {
|
||||
continue a
|
||||
}
|
||||
}
|
||||
for b.Bit(end) == 1 {
|
||||
c = append(c, Half{NI(end), labels[end]})
|
||||
b.SetBit(end, 0)
|
||||
end = int(p[end].From)
|
||||
}
|
||||
for i, j := 0, len(c)-1; i < j; i, j = i+1, j-1 {
|
||||
c[i], c[j] = c[j], c[i]
|
||||
}
|
||||
return c
|
||||
}
|
||||
return nil // no negative cycle
|
||||
}
|
||||
|
||||
// DAGMinDistPath finds a single shortest path.
|
||||
//
|
||||
// Shortest means minimum sum of arc weights.
|
||||
//
|
||||
// Returned is the path and distance as returned by FromList.PathTo.
|
||||
//
|
||||
// This is a convenience method. See DAGOptimalPaths for more options.
|
||||
func (g LabeledDirected) DAGMinDistPath(start, end NI, w WeightFunc) (LabeledPath, float64, error) {
|
||||
return g.dagPath(start, end, w, false)
|
||||
}
|
||||
|
||||
// DAGMaxDistPath finds a single longest path.
|
||||
//
|
||||
// Longest means maximum sum of arc weights.
|
||||
//
|
||||
// Returned is the path and distance as returned by FromList.PathTo.
|
||||
//
|
||||
// This is a convenience method. See DAGOptimalPaths for more options.
|
||||
func (g LabeledDirected) DAGMaxDistPath(start, end NI, w WeightFunc) (LabeledPath, float64, error) {
|
||||
return g.dagPath(start, end, w, true)
|
||||
}
|
||||
|
||||
func (g LabeledDirected) dagPath(start, end NI, w WeightFunc, longest bool) (LabeledPath, float64, error) {
|
||||
o, _ := g.Topological()
|
||||
if o == nil {
|
||||
return LabeledPath{}, 0, fmt.Errorf("not a DAG")
|
||||
}
|
||||
f, labels, dist, _ := g.DAGOptimalPaths(start, end, o, w, longest)
|
||||
if f.Paths[end].Len == 0 {
|
||||
return LabeledPath{}, 0, fmt.Errorf("no path from %d to %d", start, end)
|
||||
}
|
||||
return f.PathToLabeled(end, labels, nil), dist[end], nil
|
||||
}
|
||||
|
||||
// DAGOptimalPaths finds either longest or shortest distance paths in a
|
||||
// directed acyclic graph.
|
||||
//
|
||||
// Path distance is the sum of arc weights on the path.
|
||||
// Negative arc weights are allowed.
|
||||
// Where multiple paths exist with the same distance, the path length
|
||||
// (number of nodes) is used as a tie breaker.
|
||||
//
|
||||
// Receiver g must be a directed acyclic graph. Argument o must be either nil
|
||||
// or a topological ordering of g. If nil, a topologcal ordering is
|
||||
// computed internally. If longest is true, an optimal path is a longest
|
||||
// distance path. Otherwise it is a shortest distance path.
|
||||
//
|
||||
// Argument start is the start node for paths, end is the end node. If end
|
||||
// is a valid node number, the method returns as soon as the optimal path
|
||||
// to end is found. If end is -1, all optimal paths from start are found.
|
||||
//
|
||||
// Paths and path distances are encoded in the returned FromList, labels,
|
||||
// and dist slices. The number of nodes reached is returned as nReached.
|
||||
func (g LabeledDirected) DAGOptimalPaths(start, end NI, ordering []NI, w WeightFunc, longest bool) (f FromList, labels []LI, dist []float64, nReached int) {
|
||||
a := g.LabeledAdjacencyList
|
||||
f = NewFromList(len(a))
|
||||
f.Leaves = bits.New(len(a))
|
||||
labels = make([]LI, len(a))
|
||||
dist = make([]float64, len(a))
|
||||
if ordering == nil {
|
||||
ordering, _ = g.Topological()
|
||||
}
|
||||
// search ordering for start
|
||||
o := 0
|
||||
for ordering[o] != start {
|
||||
o++
|
||||
}
|
||||
var fBetter func(cand, ext float64) bool
|
||||
var iBetter func(cand, ext int) bool
|
||||
if longest {
|
||||
fBetter = func(cand, ext float64) bool { return cand > ext }
|
||||
iBetter = func(cand, ext int) bool { return cand > ext }
|
||||
} else {
|
||||
fBetter = func(cand, ext float64) bool { return cand < ext }
|
||||
iBetter = func(cand, ext int) bool { return cand < ext }
|
||||
}
|
||||
p := f.Paths
|
||||
p[start] = PathEnd{From: -1, Len: 1}
|
||||
f.MaxLen = 1
|
||||
leaves := &f.Leaves
|
||||
leaves.SetBit(int(start), 1)
|
||||
nReached = 1
|
||||
for n := start; n != end; n = ordering[o] {
|
||||
if p[n].Len > 0 && len(a[n]) > 0 {
|
||||
nDist := dist[n]
|
||||
candLen := p[n].Len + 1 // len for any candidate arc followed from n
|
||||
for _, to := range a[n] {
|
||||
leaves.SetBit(int(to.To), 1)
|
||||
candDist := nDist + w(to.Label)
|
||||
switch {
|
||||
case p[to.To].Len == 0: // first path to node to.To
|
||||
nReached++
|
||||
case fBetter(candDist, dist[to.To]): // better distance
|
||||
case candDist == dist[to.To] && iBetter(candLen, p[to.To].Len): // same distance but better path length
|
||||
default:
|
||||
continue
|
||||
}
|
||||
dist[to.To] = candDist
|
||||
p[to.To] = PathEnd{From: n, Len: candLen}
|
||||
labels[to.To] = to.Label
|
||||
if candLen > f.MaxLen {
|
||||
f.MaxLen = candLen
|
||||
}
|
||||
}
|
||||
leaves.SetBit(int(n), 0)
|
||||
}
|
||||
o++
|
||||
if o == len(ordering) {
|
||||
break
|
||||
}
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
// Dijkstra finds shortest paths by Dijkstra's algorithm.
|
||||
//
|
||||
// Shortest means shortest distance where distance is the
|
||||
// sum of arc weights. Where multiple paths exist with the same distance,
|
||||
// a path with the minimum number of nodes is returned.
|
||||
//
|
||||
// As usual for Dijkstra's algorithm, arc weights must be non-negative.
|
||||
// Graphs may be directed or undirected. Loops and parallel arcs are
|
||||
// allowed.
|
||||
//
|
||||
// Paths and path distances are encoded in the returned FromList and dist
|
||||
// slice. Returned labels are the labels of arcs followed to each node.
|
||||
// The number of nodes reached is returned as nReached.
|
||||
func (g LabeledAdjacencyList) Dijkstra(start, end NI, w WeightFunc) (f FromList, labels []LI, dist []float64, nReached int) {
|
||||
r := make([]tentResult, len(g))
|
||||
for i := range r {
|
||||
r[i].nx = NI(i)
|
||||
}
|
||||
f = NewFromList(len(g))
|
||||
labels = make([]LI, len(g))
|
||||
dist = make([]float64, len(g))
|
||||
current := start
|
||||
rp := f.Paths
|
||||
rp[current] = PathEnd{Len: 1, From: -1} // path length at start is 1 node
|
||||
cr := &r[current]
|
||||
cr.dist = 0 // distance at start is 0.
|
||||
cr.done = true // mark start done. it skips the heap.
|
||||
nDone := 1 // accumulated for a return value
|
||||
var t tent
|
||||
for current != end {
|
||||
nextLen := rp[current].Len + 1
|
||||
for _, nb := range g[current] {
|
||||
// d.arcVis++
|
||||
hr := &r[nb.To]
|
||||
if hr.done {
|
||||
continue // skip nodes already done
|
||||
}
|
||||
dist := cr.dist + w(nb.Label)
|
||||
vl := rp[nb.To].Len
|
||||
visited := vl > 0
|
||||
if visited {
|
||||
if dist > hr.dist {
|
||||
continue // distance is worse
|
||||
}
|
||||
// tie breaker is a nice touch and doesn't seem to
|
||||
// impact performance much.
|
||||
if dist == hr.dist && nextLen >= vl {
|
||||
continue // distance same, but number of nodes is no better
|
||||
}
|
||||
}
|
||||
// the path through current to this node is shortest so far.
|
||||
// record new path data for this node and update tentative set.
|
||||
hr.dist = dist
|
||||
rp[nb.To].Len = nextLen
|
||||
rp[nb.To].From = current
|
||||
labels[nb.To] = nb.Label
|
||||
if visited {
|
||||
heap.Fix(&t, hr.fx)
|
||||
} else {
|
||||
heap.Push(&t, hr)
|
||||
}
|
||||
}
|
||||
//d.ndVis++
|
||||
if len(t) == 0 {
|
||||
// no more reachable nodes. AllPaths normal return
|
||||
return f, labels, dist, nDone
|
||||
}
|
||||
// new current is node with smallest tentative distance
|
||||
cr = heap.Pop(&t).(*tentResult)
|
||||
cr.done = true
|
||||
nDone++
|
||||
current = cr.nx
|
||||
dist[current] = cr.dist // store final distance
|
||||
}
|
||||
// normal return for single shortest path search
|
||||
return f, labels, dist, -1
|
||||
}
|
||||
|
||||
// DijkstraPath finds a single shortest path.
|
||||
//
|
||||
// Returned is the path as returned by FromList.LabeledPathTo and the total
|
||||
// path distance.
|
||||
func (g LabeledAdjacencyList) DijkstraPath(start, end NI, w WeightFunc) (LabeledPath, float64) {
|
||||
f, labels, dist, _ := g.Dijkstra(start, end, w)
|
||||
return f.PathToLabeled(end, labels, nil), dist[end]
|
||||
}
|
||||
|
||||
// tent implements container/heap
|
||||
func (t tent) Len() int { return len(t) }
|
||||
func (t tent) Less(i, j int) bool { return t[i].dist < t[j].dist }
|
||||
func (t tent) Swap(i, j int) {
|
||||
t[i], t[j] = t[j], t[i]
|
||||
t[i].fx = i
|
||||
t[j].fx = j
|
||||
}
|
||||
func (s *tent) Push(x interface{}) {
|
||||
nd := x.(*tentResult)
|
||||
nd.fx = len(*s)
|
||||
*s = append(*s, nd)
|
||||
}
|
||||
func (s *tent) Pop() interface{} {
|
||||
t := *s
|
||||
last := len(t) - 1
|
||||
*s = t[:last]
|
||||
return t[last]
|
||||
}
|
||||
|
||||
type tentResult struct {
|
||||
dist float64 // tentative distance, sum of arc weights
|
||||
nx NI // slice index, "node id"
|
||||
fx int // heap.Fix index
|
||||
done bool
|
||||
}
|
||||
|
||||
type tent []*tentResult
|
817
vendor/github.com/soniakeys/graph/undir.go
generated
vendored
Normal file
817
vendor/github.com/soniakeys/graph/undir.go
generated
vendored
Normal file
|
@ -0,0 +1,817 @@
|
|||
// Copyright 2014 Sonia Keys
|
||||
// License MIT: http://opensource.org/licenses/MIT
|
||||
|
||||
package graph
|
||||
|
||||
// undir.go has methods specific to undirected graphs, Undirected and
|
||||
// LabeledUndirected.
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
|
||||
"github.com/soniakeys/bits"
|
||||
)
|
||||
|
||||
// AddEdge adds an edge to a graph.
|
||||
//
|
||||
// It can be useful for constructing undirected graphs.
|
||||
//
|
||||
// When n1 and n2 are distinct, it adds the arc n1->n2 and the reciprocal
|
||||
// n2->n1. When n1 and n2 are the same, it adds a single arc loop.
|
||||
//
|
||||
// The pointer receiver allows the method to expand the graph as needed
|
||||
// to include the values n1 and n2. If n1 or n2 happen to be greater than
|
||||
// len(*p) the method does not panic, but simply expands the graph.
|
||||
//
|
||||
// If you know or can compute the final graph order however, consider
|
||||
// preallocating to avoid any overhead of expanding the graph.
|
||||
// See second example, "More".
|
||||
func (p *Undirected) AddEdge(n1, n2 NI) {
|
||||
// Similar code in LabeledAdjacencyList.AddEdge.
|
||||
|
||||
// determine max of the two end points
|
||||
max := n1
|
||||
if n2 > max {
|
||||
max = n2
|
||||
}
|
||||
// expand graph if needed, to include both
|
||||
g := p.AdjacencyList
|
||||
if int(max) >= len(g) {
|
||||
p.AdjacencyList = make(AdjacencyList, max+1)
|
||||
copy(p.AdjacencyList, g)
|
||||
g = p.AdjacencyList
|
||||
}
|
||||
// create one half-arc,
|
||||
g[n1] = append(g[n1], n2)
|
||||
// and except for loops, create the reciprocal
|
||||
if n1 != n2 {
|
||||
g[n2] = append(g[n2], n1)
|
||||
}
|
||||
}
|
||||
|
||||
// RemoveEdge removes a single edge between nodes n1 and n2.
|
||||
//
|
||||
// It removes reciprocal arcs in the case of distinct n1 and n2 or removes
|
||||
// a single arc loop in the case of n1 == n2.
|
||||
//
|
||||
// Returns true if the specified edge is found and successfully removed,
|
||||
// false if the edge does not exist.
|
||||
func (g Undirected) RemoveEdge(n1, n2 NI) (ok bool) {
|
||||
ok, x1, x2 := g.HasEdge(n1, n2)
|
||||
if !ok {
|
||||
return
|
||||
}
|
||||
a := g.AdjacencyList
|
||||
to := a[n1]
|
||||
last := len(to) - 1
|
||||
to[x1] = to[last]
|
||||
a[n1] = to[:last]
|
||||
if n1 == n2 {
|
||||
return
|
||||
}
|
||||
to = a[n2]
|
||||
last = len(to) - 1
|
||||
to[x2] = to[last]
|
||||
a[n2] = to[:last]
|
||||
return
|
||||
}
|
||||
|
||||
// ArcDensity returns density for a simple directed graph.
|
||||
//
|
||||
// Parameter n is order, or number of nodes of a simple directed graph.
|
||||
// Parameter a is the arc size, or number of directed arcs.
|
||||
//
|
||||
// Returned density is the fraction `a` over the total possible number of arcs
|
||||
// or a / (n * (n-1)).
|
||||
//
|
||||
// See also Density for density of a simple undirected graph.
|
||||
//
|
||||
// See also the corresponding methods AdjacencyList.ArcDensity and
|
||||
// LabeledAdjacencyList.ArcDensity.
|
||||
func ArcDensity(n, a int) float64 {
|
||||
return float64(a) / (float64(n) * float64(n-1))
|
||||
}
|
||||
|
||||
// Density returns density for a simple undirected graph.
|
||||
//
|
||||
// Parameter n is order, or number of nodes of a simple undirected graph.
|
||||
// Parameter m is the size, or number of undirected edges.
|
||||
//
|
||||
// Returned density is the fraction m over the total possible number of edges
|
||||
// or m / ((n * (n-1))/2).
|
||||
//
|
||||
// See also ArcDensity for simple directed graphs.
|
||||
//
|
||||
// See also the corresponding methods AdjacencyList.Density and
|
||||
// LabeledAdjacencyList.Density.
|
||||
func Density(n, m int) float64 {
|
||||
return float64(m) * 2 / (float64(n) * float64(n-1))
|
||||
}
|
||||
|
||||
// An EdgeVisitor is an argument to some traversal methods.
|
||||
//
|
||||
// Traversal methods call the visitor function for each edge visited.
|
||||
// Argument e is the edge being visited.
|
||||
type EdgeVisitor func(e Edge)
|
||||
|
||||
// Edges iterates over the edges of an undirected graph.
|
||||
//
|
||||
// Edge visitor v is called for each edge of the graph. That is, it is called
|
||||
// once for each reciprocal arc pair and once for each loop.
|
||||
//
|
||||
// See also LabeledUndirected.Edges for a labeled version.
|
||||
// See also Undirected.SimpleEdges for a version that emits only the simple
|
||||
// subgraph.
|
||||
func (g Undirected) Edges(v EdgeVisitor) {
|
||||
a := g.AdjacencyList
|
||||
unpaired := make(AdjacencyList, len(a))
|
||||
for fr, to := range a {
|
||||
arc: // for each arc in a
|
||||
for _, to := range to {
|
||||
if to == NI(fr) {
|
||||
v(Edge{NI(fr), to}) // output loop
|
||||
continue
|
||||
}
|
||||
// search unpaired arcs
|
||||
ut := unpaired[to]
|
||||
for i, u := range ut {
|
||||
if u == NI(fr) { // found reciprocal
|
||||
v(Edge{u, to}) // output edge
|
||||
last := len(ut) - 1
|
||||
ut[i] = ut[last]
|
||||
unpaired[to] = ut[:last]
|
||||
continue arc
|
||||
}
|
||||
}
|
||||
// reciprocal not found
|
||||
unpaired[fr] = append(unpaired[fr], to)
|
||||
}
|
||||
}
|
||||
// undefined behavior is that unpaired arcs are silently ignored.
|
||||
}
|
||||
|
||||
// FromList builds a forest with a tree spanning each connected component.
|
||||
//
|
||||
// For each component a root is chosen and spanning is done with the method
|
||||
// Undirected.SpanTree, and so is breadth-first. Returned is a FromList with
|
||||
// all spanned trees, a list of roots chosen, and a bool indicating if the
|
||||
// receiver graph g was found to be a simple graph connected as a forest.
|
||||
// Any cycles, loops, or parallel edges in any component will cause
|
||||
// simpleForest to be false, but FromList f will still be populated with
|
||||
// a valid and complete spanning forest.
|
||||
func (g Undirected) FromList() (f FromList, roots []NI, simpleForest bool) {
|
||||
p := make([]PathEnd, g.Order())
|
||||
for i := range p {
|
||||
p[i].From = -1
|
||||
}
|
||||
f.Paths = p
|
||||
simpleForest = true
|
||||
ts := 0
|
||||
for n := range g.AdjacencyList {
|
||||
if p[n].From >= 0 {
|
||||
continue
|
||||
}
|
||||
roots = append(roots, NI(n))
|
||||
ns, st := g.SpanTree(NI(n), &f)
|
||||
if !st {
|
||||
simpleForest = false
|
||||
}
|
||||
ts += ns
|
||||
if ts == len(p) {
|
||||
break
|
||||
}
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
// HasEdge returns true if g has any edge between nodes n1 and n2.
|
||||
//
|
||||
// Also returned are indexes x1 and x2 such that g[n1][x1] == n2
|
||||
// and g[n2][x2] == n1. If no edge between n1 and n2 is present HasArc
|
||||
// returns `has` == false.
|
||||
//
|
||||
// See also HasArc. If you are interested only in the boolean result and
|
||||
// g is a well formed (passes IsUndirected) then HasArc is an adequate test.
|
||||
func (g Undirected) HasEdge(n1, n2 NI) (has bool, x1, x2 int) {
|
||||
if has, x1 = g.HasArc(n1, n2); !has {
|
||||
return has, x1, x1
|
||||
}
|
||||
has, x2 = g.HasArc(n2, n1)
|
||||
return
|
||||
}
|
||||
|
||||
// SimpleEdges iterates over the edges of the simple subgraph of an undirected
|
||||
// graph.
|
||||
//
|
||||
// Edge visitor v is called for each pair of distinct nodes that is connected
|
||||
// with an edge. That is, loops are ignored and parallel edges are reduced to
|
||||
// a single edge.
|
||||
//
|
||||
// See also Undirected.Edges for a version that emits all edges.
|
||||
func (g Undirected) SimpleEdges(v EdgeVisitor) {
|
||||
for fr, to := range g.AdjacencyList {
|
||||
e := bits.New(len(g.AdjacencyList))
|
||||
for _, to := range to {
|
||||
if to > NI(fr) && e.Bit(int(to)) == 0 {
|
||||
e.SetBit(int(to), 1)
|
||||
v(Edge{NI(fr), to})
|
||||
}
|
||||
}
|
||||
}
|
||||
// undefined behavior is that unpaired arcs may or may not be emitted.
|
||||
}
|
||||
|
||||
// SpanTree builds a tree spanning a connected component.
|
||||
//
|
||||
// The component is spanned by breadth-first search from the given root.
|
||||
// The resulting spanning tree in stored a FromList.
|
||||
//
|
||||
// If FromList.Paths is not the same length as g, it is allocated and
|
||||
// initialized. This allows a zero value FromList to be passed as f.
|
||||
// If FromList.Paths is the same length as g, it is used as is and is not
|
||||
// reinitialized. This allows multiple trees to be spanned in the same
|
||||
// FromList with successive calls.
|
||||
//
|
||||
// For nodes spanned, the Path member of the returned FromList is populated
|
||||
// with both From and Len values. The MaxLen member will be updated but
|
||||
// not Leaves.
|
||||
//
|
||||
// Returned is the number of nodes spanned, which will be the number of nodes
|
||||
// in the component, and a bool indicating if the component was found to be a
|
||||
// simply connected unrooted tree in the receiver graph g. Any cycles, loops,
|
||||
// or parallel edges in the component will cause simpleTree to be false, but
|
||||
// FromList f will still be populated with a valid and complete spanning tree.
|
||||
func (g Undirected) SpanTree(root NI, f *FromList) (nSpanned int, simpleTree bool) {
|
||||
a := g.AdjacencyList
|
||||
p := f.Paths
|
||||
if len(p) != len(a) {
|
||||
p = make([]PathEnd, len(a))
|
||||
for i := range p {
|
||||
p[i].From = -1
|
||||
}
|
||||
f.Paths = p
|
||||
}
|
||||
simpleTree = true
|
||||
p[root] = PathEnd{From: -1, Len: 1}
|
||||
type arc struct {
|
||||
from NI
|
||||
half NI
|
||||
}
|
||||
var next []arc
|
||||
frontier := []arc{{-1, root}}
|
||||
for len(frontier) > 0 {
|
||||
for _, fa := range frontier { // fa frontier arc
|
||||
nSpanned++
|
||||
l := p[fa.half].Len + 1
|
||||
for _, to := range a[fa.half] {
|
||||
if to == fa.from {
|
||||
continue
|
||||
}
|
||||
if p[to].Len > 0 {
|
||||
simpleTree = false
|
||||
continue
|
||||
}
|
||||
p[to] = PathEnd{From: fa.half, Len: l}
|
||||
if l > f.MaxLen {
|
||||
f.MaxLen = l
|
||||
}
|
||||
next = append(next, arc{fa.half, to})
|
||||
}
|
||||
}
|
||||
frontier, next = next, frontier[:0]
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
// TarjanBiconnectedComponents decomposes a graph into maximal biconnected
|
||||
// components, components for which if any node were removed the component
|
||||
// would remain connected.
|
||||
//
|
||||
// The receiver g must be a simple graph. The method calls the emit argument
|
||||
// for each component identified, as long as emit returns true. If emit
|
||||
// returns false, TarjanBiconnectedComponents returns immediately.
|
||||
//
|
||||
// See also the eqivalent labeled TarjanBiconnectedComponents.
|
||||
func (g Undirected) TarjanBiconnectedComponents(emit func([]Edge) bool) {
|
||||
// Implemented closely to pseudocode in "Depth-first search and linear
|
||||
// graph algorithms", Robert Tarjan, SIAM J. Comput. Vol. 1, No. 2,
|
||||
// June 1972.
|
||||
//
|
||||
// Note Tarjan's "adjacency structure" is graph.AdjacencyList,
|
||||
// His "adjacency list" is an element of a graph.AdjacencyList, also
|
||||
// termed a "to-list", "neighbor list", or "child list."
|
||||
a := g.AdjacencyList
|
||||
number := make([]int, len(a))
|
||||
lowpt := make([]int, len(a))
|
||||
var stack []Edge
|
||||
var i int
|
||||
var biconnect func(NI, NI) bool
|
||||
biconnect = func(v, u NI) bool {
|
||||
i++
|
||||
number[v] = i
|
||||
lowpt[v] = i
|
||||
for _, w := range a[v] {
|
||||
if number[w] == 0 {
|
||||
stack = append(stack, Edge{v, w})
|
||||
if !biconnect(w, v) {
|
||||
return false
|
||||
}
|
||||
if lowpt[w] < lowpt[v] {
|
||||
lowpt[v] = lowpt[w]
|
||||
}
|
||||
if lowpt[w] >= number[v] {
|
||||
var bcc []Edge
|
||||
top := len(stack) - 1
|
||||
for number[stack[top].N1] >= number[w] {
|
||||
bcc = append(bcc, stack[top])
|
||||
stack = stack[:top]
|
||||
top--
|
||||
}
|
||||
bcc = append(bcc, stack[top])
|
||||
stack = stack[:top]
|
||||
top--
|
||||
if !emit(bcc) {
|
||||
return false
|
||||
}
|
||||
}
|
||||
} else if number[w] < number[v] && w != u {
|
||||
stack = append(stack, Edge{v, w})
|
||||
if number[w] < lowpt[v] {
|
||||
lowpt[v] = number[w]
|
||||
}
|
||||
}
|
||||
}
|
||||
return true
|
||||
}
|
||||
for w := range a {
|
||||
if number[w] == 0 && !biconnect(NI(w), -1) {
|
||||
return
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func (g Undirected) BlockCut(block func([]Edge) bool, cut func(NI) bool, isolated func(NI) bool) {
|
||||
a := g.AdjacencyList
|
||||
number := make([]int, len(a))
|
||||
lowpt := make([]int, len(a))
|
||||
var stack []Edge
|
||||
var i, rc int
|
||||
var biconnect func(NI, NI) bool
|
||||
biconnect = func(v, u NI) bool {
|
||||
i++
|
||||
number[v] = i
|
||||
lowpt[v] = i
|
||||
for _, w := range a[v] {
|
||||
if number[w] == 0 {
|
||||
if u < 0 {
|
||||
rc++
|
||||
}
|
||||
stack = append(stack, Edge{v, w})
|
||||
if !biconnect(w, v) {
|
||||
return false
|
||||
}
|
||||
if lowpt[w] < lowpt[v] {
|
||||
lowpt[v] = lowpt[w]
|
||||
}
|
||||
if lowpt[w] >= number[v] {
|
||||
if u >= 0 && !cut(v) {
|
||||
return false
|
||||
}
|
||||
var bcc []Edge
|
||||
top := len(stack) - 1
|
||||
for number[stack[top].N1] >= number[w] {
|
||||
bcc = append(bcc, stack[top])
|
||||
stack = stack[:top]
|
||||
top--
|
||||
}
|
||||
bcc = append(bcc, stack[top])
|
||||
stack = stack[:top]
|
||||
top--
|
||||
if !block(bcc) {
|
||||
return false
|
||||
}
|
||||
}
|
||||
} else if number[w] < number[v] && w != u {
|
||||
stack = append(stack, Edge{v, w})
|
||||
if number[w] < lowpt[v] {
|
||||
lowpt[v] = number[w]
|
||||
}
|
||||
}
|
||||
}
|
||||
if u < 0 && rc > 1 {
|
||||
return cut(v)
|
||||
}
|
||||
return true
|
||||
}
|
||||
for w := range a {
|
||||
if number[w] > 0 {
|
||||
continue
|
||||
}
|
||||
if len(a[w]) == 0 {
|
||||
if !isolated(NI(w)) {
|
||||
return
|
||||
}
|
||||
continue
|
||||
}
|
||||
rc = 0
|
||||
if !biconnect(NI(w), -1) {
|
||||
return
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// AddEdge adds an edge to a labeled graph.
|
||||
//
|
||||
// It can be useful for constructing undirected graphs.
|
||||
//
|
||||
// When n1 and n2 are distinct, it adds the arc n1->n2 and the reciprocal
|
||||
// n2->n1. When n1 and n2 are the same, it adds a single arc loop.
|
||||
//
|
||||
// If the edge already exists in *p, a parallel edge is added.
|
||||
//
|
||||
// The pointer receiver allows the method to expand the graph as needed
|
||||
// to include the values n1 and n2. If n1 or n2 happen to be greater than
|
||||
// len(*p) the method does not panic, but simply expands the graph.
|
||||
func (p *LabeledUndirected) AddEdge(e Edge, l LI) {
|
||||
// Similar code in AdjacencyList.AddEdge.
|
||||
|
||||
// determine max of the two end points
|
||||
max := e.N1
|
||||
if e.N2 > max {
|
||||
max = e.N2
|
||||
}
|
||||
// expand graph if needed, to include both
|
||||
g := p.LabeledAdjacencyList
|
||||
if max >= NI(len(g)) {
|
||||
p.LabeledAdjacencyList = make(LabeledAdjacencyList, max+1)
|
||||
copy(p.LabeledAdjacencyList, g)
|
||||
g = p.LabeledAdjacencyList
|
||||
}
|
||||
// create one half-arc,
|
||||
g[e.N1] = append(g[e.N1], Half{To: e.N2, Label: l})
|
||||
// and except for loops, create the reciprocal
|
||||
if e.N1 != e.N2 {
|
||||
g[e.N2] = append(g[e.N2], Half{To: e.N1, Label: l})
|
||||
}
|
||||
}
|
||||
|
||||
// A LabeledEdgeVisitor is an argument to some traversal methods.
|
||||
//
|
||||
// Traversal methods call the visitor function for each edge visited.
|
||||
// Argument e is the edge being visited.
|
||||
type LabeledEdgeVisitor func(e LabeledEdge)
|
||||
|
||||
// Edges iterates over the edges of a labeled undirected graph.
|
||||
//
|
||||
// Edge visitor v is called for each edge of the graph. That is, it is called
|
||||
// once for each reciprocal arc pair and once for each loop.
|
||||
//
|
||||
// See also Undirected.Edges for an unlabeled version.
|
||||
// See also the more simplistic LabeledAdjacencyList.ArcsAsEdges.
|
||||
func (g LabeledUndirected) Edges(v LabeledEdgeVisitor) {
|
||||
// similar code in LabeledAdjacencyList.InUndirected
|
||||
a := g.LabeledAdjacencyList
|
||||
unpaired := make(LabeledAdjacencyList, len(a))
|
||||
for fr, to := range a {
|
||||
arc: // for each arc in a
|
||||
for _, to := range to {
|
||||
if to.To == NI(fr) {
|
||||
v(LabeledEdge{Edge{NI(fr), to.To}, to.Label}) // output loop
|
||||
continue
|
||||
}
|
||||
// search unpaired arcs
|
||||
ut := unpaired[to.To]
|
||||
for i, u := range ut {
|
||||
if u.To == NI(fr) && u.Label == to.Label { // found reciprocal
|
||||
v(LabeledEdge{Edge{NI(fr), to.To}, to.Label}) // output edge
|
||||
last := len(ut) - 1
|
||||
ut[i] = ut[last]
|
||||
unpaired[to.To] = ut[:last]
|
||||
continue arc
|
||||
}
|
||||
}
|
||||
// reciprocal not found
|
||||
unpaired[fr] = append(unpaired[fr], to)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// FromList builds a forest with a tree spanning each connected component in g.
|
||||
//
|
||||
// A root is chosen and spanning is done with the LabeledUndirected.SpanTree
|
||||
// method, and so is breadth-first. Returned is a FromList with all spanned
|
||||
// trees, labels corresponding to arcs in f,
|
||||
// a list of roots chosen, and a bool indicating if the receiver graph g was
|
||||
// found to be a simple graph connected as a forest. Any cycles, loops, or
|
||||
// parallel edges in any component will cause simpleForest to be false, but
|
||||
// FromList f will still be populated with a valid and complete spanning forest.
|
||||
|
||||
// FromList builds a forest with a tree spanning each connected component.
|
||||
//
|
||||
// For each component a root is chosen and spanning is done with the method
|
||||
// Undirected.SpanTree, and so is breadth-first. Returned is a FromList with
|
||||
// all spanned trees, labels corresponding to arcs in f, a list of roots
|
||||
// chosen, and a bool indicating if the receiver graph g was found to be a
|
||||
// simple graph connected as a forest. Any cycles, loops, or parallel edges
|
||||
// in any component will cause simpleForest to be false, but FromList f will
|
||||
// still be populated with a valid and complete spanning forest.
|
||||
func (g LabeledUndirected) FromList() (f FromList, labels []LI, roots []NI, simpleForest bool) {
|
||||
p := make([]PathEnd, g.Order())
|
||||
for i := range p {
|
||||
p[i].From = -1
|
||||
}
|
||||
f.Paths = p
|
||||
labels = make([]LI, len(p))
|
||||
simpleForest = true
|
||||
ts := 0
|
||||
for n := range g.LabeledAdjacencyList {
|
||||
if p[n].From >= 0 {
|
||||
continue
|
||||
}
|
||||
roots = append(roots, NI(n))
|
||||
ns, st := g.SpanTree(NI(n), &f, labels)
|
||||
if !st {
|
||||
simpleForest = false
|
||||
}
|
||||
ts += ns
|
||||
if ts == len(p) {
|
||||
break
|
||||
}
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
// SpanTree builds a tree spanning a connected component.
|
||||
//
|
||||
// The component is spanned by breadth-first search from the given root.
|
||||
// The resulting spanning tree in stored a FromList, and arc labels optionally
|
||||
// stored in a slice.
|
||||
//
|
||||
// If FromList.Paths is not the same length as g, it is allocated and
|
||||
// initialized. This allows a zero value FromList to be passed as f.
|
||||
// If FromList.Paths is the same length as g, it is used as is and is not
|
||||
// reinitialized. This allows multiple trees to be spanned in the same
|
||||
// FromList with successive calls.
|
||||
//
|
||||
// For nodes spanned, the Path member of returned FromList f is populated
|
||||
// populated with both From and Len values. The MaxLen member will be
|
||||
// updated but not Leaves.
|
||||
//
|
||||
// The labels slice will be populated only if it is same length as g.
|
||||
// Nil can be passed for example if labels are not needed.
|
||||
//
|
||||
// Returned is the number of nodes spanned, which will be the number of nodes
|
||||
// in the component, and a bool indicating if the component was found to be a
|
||||
// simply connected unrooted tree in the receiver graph g. Any cycles, loops,
|
||||
// or parallel edges in the component will cause simpleTree to be false, but
|
||||
// FromList f will still be populated with a valid and complete spanning tree.
|
||||
func (g LabeledUndirected) SpanTree(root NI, f *FromList, labels []LI) (nSpanned int, simple bool) {
|
||||
a := g.LabeledAdjacencyList
|
||||
p := f.Paths
|
||||
if len(p) != len(a) {
|
||||
p = make([]PathEnd, len(a))
|
||||
for i := range p {
|
||||
p[i].From = -1
|
||||
}
|
||||
f.Paths = p
|
||||
}
|
||||
simple = true
|
||||
p[root].Len = 1
|
||||
type arc struct {
|
||||
from NI
|
||||
half Half
|
||||
}
|
||||
var next []arc
|
||||
frontier := []arc{{-1, Half{root, -1}}}
|
||||
for len(frontier) > 0 {
|
||||
for _, fa := range frontier { // fa frontier arc
|
||||
nSpanned++
|
||||
l := p[fa.half.To].Len + 1
|
||||
for _, to := range a[fa.half.To] {
|
||||
if to.To == fa.from && to.Label == fa.half.Label {
|
||||
continue
|
||||
}
|
||||
if p[to.To].Len > 0 {
|
||||
simple = false
|
||||
continue
|
||||
}
|
||||
p[to.To] = PathEnd{From: fa.half.To, Len: l}
|
||||
if len(labels) == len(p) {
|
||||
labels[to.To] = to.Label
|
||||
}
|
||||
if l > f.MaxLen {
|
||||
f.MaxLen = l
|
||||
}
|
||||
next = append(next, arc{fa.half.To, to})
|
||||
}
|
||||
}
|
||||
frontier, next = next, frontier[:0]
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
// HasEdge returns true if g has any edge between nodes n1 and n2.
|
||||
//
|
||||
// Also returned are indexes x1 and x2 such that g[n1][x1] == Half{n2, l}
|
||||
// and g[n2][x2] == {n1, l} for some label l. If no edge between n1 and n2
|
||||
// exists, HasArc returns `has` == false.
|
||||
//
|
||||
// See also HasArc. If you are only interested in the boolean result then
|
||||
// HasArc is an adequate test.
|
||||
func (g LabeledUndirected) HasEdge(n1, n2 NI) (has bool, x1, x2 int) {
|
||||
if has, x1 = g.HasArc(n1, n2); !has {
|
||||
return has, x1, x1
|
||||
}
|
||||
has, x2 = g.HasArcLabel(n2, n1, g.LabeledAdjacencyList[n1][x1].Label)
|
||||
return
|
||||
}
|
||||
|
||||
// HasEdgeLabel returns true if g has any edge between nodes n1 and n2 with
|
||||
// label l.
|
||||
//
|
||||
// Also returned are indexes x1 and x2 such that g[n1][x1] == Half{n2, l}
|
||||
// and g[n2][x2] == Half{n1, l}. If no edge between n1 and n2 with label l
|
||||
// is present HasArc returns `has` == false.
|
||||
func (g LabeledUndirected) HasEdgeLabel(n1, n2 NI, l LI) (has bool, x1, x2 int) {
|
||||
if has, x1 = g.HasArcLabel(n1, n2, l); !has {
|
||||
return has, x1, x1
|
||||
}
|
||||
has, x2 = g.HasArcLabel(n2, n1, l)
|
||||
return
|
||||
}
|
||||
|
||||
// RemoveEdge removes a single edge between nodes n1 and n2.
|
||||
//
|
||||
// It removes reciprocal arcs in the case of distinct n1 and n2 or removes
|
||||
// a single arc loop in the case of n1 == n2.
|
||||
//
|
||||
// If the specified edge is found and successfully removed, RemoveEdge returns
|
||||
// true and the label of the edge removed. If no edge exists between n1 and n2,
|
||||
// RemoveEdge returns false, 0.
|
||||
func (g LabeledUndirected) RemoveEdge(n1, n2 NI) (ok bool, label LI) {
|
||||
ok, x1, x2 := g.HasEdge(n1, n2)
|
||||
if !ok {
|
||||
return
|
||||
}
|
||||
a := g.LabeledAdjacencyList
|
||||
to := a[n1]
|
||||
label = to[x1].Label // return value
|
||||
last := len(to) - 1
|
||||
to[x1] = to[last]
|
||||
a[n1] = to[:last]
|
||||
if n1 == n2 {
|
||||
return
|
||||
}
|
||||
to = a[n2]
|
||||
last = len(to) - 1
|
||||
to[x2] = to[last]
|
||||
a[n2] = to[:last]
|
||||
return
|
||||
}
|
||||
|
||||
// RemoveEdgeLabel removes a single edge between nodes n1 and n2 with label l.
|
||||
//
|
||||
// It removes reciprocal arcs in the case of distinct n1 and n2 or removes
|
||||
// a single arc loop in the case of n1 == n2.
|
||||
//
|
||||
// Returns true if the specified edge is found and successfully removed,
|
||||
// false if the edge does not exist.
|
||||
func (g LabeledUndirected) RemoveEdgeLabel(n1, n2 NI, l LI) (ok bool) {
|
||||
ok, x1, x2 := g.HasEdgeLabel(n1, n2, l)
|
||||
if !ok {
|
||||
return
|
||||
}
|
||||
a := g.LabeledAdjacencyList
|
||||
to := a[n1]
|
||||
last := len(to) - 1
|
||||
to[x1] = to[last]
|
||||
a[n1] = to[:last]
|
||||
if n1 == n2 {
|
||||
return
|
||||
}
|
||||
to = a[n2]
|
||||
last = len(to) - 1
|
||||
to[x2] = to[last]
|
||||
a[n2] = to[:last]
|
||||
return
|
||||
}
|
||||
|
||||
// TarjanBiconnectedComponents decomposes a graph into maximal biconnected
|
||||
// components, components for which if any node were removed the component
|
||||
// would remain connected.
|
||||
//
|
||||
// The receiver g must be a simple graph. The method calls the emit argument
|
||||
// for each component identified, as long as emit returns true. If emit
|
||||
// returns false, TarjanBiconnectedComponents returns immediately.
|
||||
//
|
||||
// See also the eqivalent unlabeled TarjanBiconnectedComponents.
|
||||
func (g LabeledUndirected) TarjanBiconnectedComponents(emit func([]LabeledEdge) bool) {
|
||||
// Code nearly identical to unlabled version.
|
||||
number := make([]int, g.Order())
|
||||
lowpt := make([]int, g.Order())
|
||||
var stack []LabeledEdge
|
||||
var i int
|
||||
var biconnect func(NI, NI) bool
|
||||
biconnect = func(v, u NI) bool {
|
||||
i++
|
||||
number[v] = i
|
||||
lowpt[v] = i
|
||||
for _, w := range g.LabeledAdjacencyList[v] {
|
||||
if number[w.To] == 0 {
|
||||
stack = append(stack, LabeledEdge{Edge{v, w.To}, w.Label})
|
||||
if !biconnect(w.To, v) {
|
||||
return false
|
||||
}
|
||||
if lowpt[w.To] < lowpt[v] {
|
||||
lowpt[v] = lowpt[w.To]
|
||||
}
|
||||
if lowpt[w.To] >= number[v] {
|
||||
var bcc []LabeledEdge
|
||||
top := len(stack) - 1
|
||||
for number[stack[top].N1] >= number[w.To] {
|
||||
bcc = append(bcc, stack[top])
|
||||
stack = stack[:top]
|
||||
top--
|
||||
}
|
||||
bcc = append(bcc, stack[top])
|
||||
stack = stack[:top]
|
||||
top--
|
||||
if !emit(bcc) {
|
||||
return false
|
||||
}
|
||||
}
|
||||
} else if number[w.To] < number[v] && w.To != u {
|
||||
stack = append(stack, LabeledEdge{Edge{v, w.To}, w.Label})
|
||||
if number[w.To] < lowpt[v] {
|
||||
lowpt[v] = number[w.To]
|
||||
}
|
||||
}
|
||||
}
|
||||
return true
|
||||
}
|
||||
for w := range g.LabeledAdjacencyList {
|
||||
if number[w] == 0 && !biconnect(NI(w), -1) {
|
||||
return
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func (e *eulerian) pushUndir() error {
|
||||
for u := e.top(); ; {
|
||||
e.uv.SetBit(int(u), 0)
|
||||
arcs := e.g[u]
|
||||
if len(arcs) == 0 {
|
||||
return nil
|
||||
}
|
||||
w := arcs[0]
|
||||
e.s++
|
||||
e.p[e.s] = w
|
||||
e.g[u] = arcs[1:] // consume arc
|
||||
// difference from directed counterpart in dir.go:
|
||||
// as long as it's not a loop, consume reciprocal arc as well
|
||||
if w != u {
|
||||
a2 := e.g[w]
|
||||
for x, rx := range a2 {
|
||||
if rx == u { // here it is
|
||||
last := len(a2) - 1
|
||||
a2[x] = a2[last] // someone else gets the seat
|
||||
e.g[w] = a2[:last] // and it's gone.
|
||||
goto l
|
||||
}
|
||||
}
|
||||
return fmt.Errorf("graph not undirected. %d -> %d reciprocal not found", u, w)
|
||||
}
|
||||
l:
|
||||
u = w
|
||||
}
|
||||
}
|
||||
|
||||
func (e *labEulerian) pushUndir() error {
|
||||
for u := e.top(); ; {
|
||||
e.uv.SetBit(int(u.To), 0)
|
||||
arcs := e.g[u.To]
|
||||
if len(arcs) == 0 {
|
||||
return nil
|
||||
}
|
||||
w := arcs[0]
|
||||
e.s++
|
||||
e.p[e.s] = w
|
||||
e.g[u.To] = arcs[1:] // consume arc
|
||||
// difference from directed counterpart in dir.go:
|
||||
// as long as it's not a loop, consume reciprocal arc as well
|
||||
if w.To != u.To {
|
||||
a2 := e.g[w.To]
|
||||
for x, rx := range a2 {
|
||||
if rx.To == u.To && rx.Label == w.Label { // here it is
|
||||
last := len(a2) - 1
|
||||
a2[x] = a2[last] // someone else can have the seat
|
||||
e.g[w.To] = a2[:last] // and it's gone.
|
||||
goto l
|
||||
}
|
||||
}
|
||||
return fmt.Errorf("graph not undirected. %d -> %v reciprocal not found", u.To, w)
|
||||
}
|
||||
l:
|
||||
u = w
|
||||
}
|
||||
}
|
1138
vendor/github.com/soniakeys/graph/undir_RO.go
generated
vendored
Normal file
1138
vendor/github.com/soniakeys/graph/undir_RO.go
generated
vendored
Normal file
File diff suppressed because it is too large
Load diff
1138
vendor/github.com/soniakeys/graph/undir_cg.go
generated
vendored
Normal file
1138
vendor/github.com/soniakeys/graph/undir_cg.go
generated
vendored
Normal file
File diff suppressed because it is too large
Load diff
4
vendor/golang.org/x/crypto/ssh/knownhosts/knownhosts.go
generated
vendored
4
vendor/golang.org/x/crypto/ssh/knownhosts/knownhosts.go
generated
vendored
|
@ -350,8 +350,8 @@ func (db *hostKeyDB) check(address string, remote net.Addr, remoteKey ssh.Public
|
|||
return db.checkAddr(hostToCheck, remoteKey)
|
||||
}
|
||||
|
||||
// checkAddrs checks if we can find the given public key for any of
|
||||
// the given addresses. If we only find an entry for the IP address,
|
||||
// checkAddr checks if we can find the given public key for the
|
||||
// given address. If we only find an entry for the IP address,
|
||||
// or only the hostname, then this still succeeds.
|
||||
func (db *hostKeyDB) checkAddr(a addr, remoteKey ssh.PublicKey) error {
|
||||
// TODO(hanwen): are these the right semantics? What if there
|
||||
|
|
4
vendor/golang.org/x/crypto/ssh/terminal/terminal.go
generated
vendored
4
vendor/golang.org/x/crypto/ssh/terminal/terminal.go
generated
vendored
|
@ -159,6 +159,10 @@ func bytesToKey(b []byte, pasteActive bool) (rune, []byte) {
|
|||
return keyClearScreen, b[1:]
|
||||
case 23: // ^W
|
||||
return keyDeleteWord, b[1:]
|
||||
case 14: // ^N
|
||||
return keyDown, b[1:]
|
||||
case 16: // ^P
|
||||
return keyUp, b[1:]
|
||||
}
|
||||
}
|
||||
|
||||
|
|
9
vendor/golang.org/x/net/proxy/proxy.go
generated
vendored
9
vendor/golang.org/x/net/proxy/proxy.go
generated
vendored
|
@ -79,8 +79,13 @@ func FromURL(u *url.URL, forward Dialer) (Dialer, error) {
|
|||
}
|
||||
|
||||
switch u.Scheme {
|
||||
case "socks5":
|
||||
return SOCKS5("tcp", u.Host, auth, forward)
|
||||
case "socks5", "socks5h":
|
||||
addr := u.Hostname()
|
||||
port := u.Port()
|
||||
if port == "" {
|
||||
port = "1080"
|
||||
}
|
||||
return SOCKS5("tcp", net.JoinHostPort(addr, port), auth, forward)
|
||||
}
|
||||
|
||||
// If the scheme doesn't match any of the built-in schemes, see if it
|
||||
|
|
29
vendor/golang.org/x/sys/unix/asm_netbsd_arm64.s
generated
vendored
Normal file
29
vendor/golang.org/x/sys/unix/asm_netbsd_arm64.s
generated
vendored
Normal file
|
@ -0,0 +1,29 @@
|
|||
// Copyright 2019 The Go Authors. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
// +build !gccgo
|
||||
|
||||
#include "textflag.h"
|
||||
|
||||
//
|
||||
// System call support for ARM64, NetBSD
|
||||
//
|
||||
|
||||
// Just jump to package syscall's implementation for all these functions.
|
||||
// The runtime may know about them.
|
||||
|
||||
TEXT ·Syscall(SB),NOSPLIT,$0-56
|
||||
B syscall·Syscall(SB)
|
||||
|
||||
TEXT ·Syscall6(SB),NOSPLIT,$0-80
|
||||
B syscall·Syscall6(SB)
|
||||
|
||||
TEXT ·Syscall9(SB),NOSPLIT,$0-104
|
||||
B syscall·Syscall9(SB)
|
||||
|
||||
TEXT ·RawSyscall(SB),NOSPLIT,$0-56
|
||||
B syscall·RawSyscall(SB)
|
||||
|
||||
TEXT ·RawSyscall6(SB),NOSPLIT,$0-80
|
||||
B syscall·RawSyscall6(SB)
|
2
vendor/golang.org/x/sys/unix/fcntl.go
generated
vendored
2
vendor/golang.org/x/sys/unix/fcntl.go
generated
vendored
|
@ -2,7 +2,7 @@
|
|||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
// +build darwin dragonfly freebsd linux netbsd openbsd
|
||||
// +build dragonfly freebsd linux netbsd openbsd
|
||||
|
||||
package unix
|
||||
|
||||
|
|
18
vendor/golang.org/x/sys/unix/fcntl_darwin.go
generated
vendored
Normal file
18
vendor/golang.org/x/sys/unix/fcntl_darwin.go
generated
vendored
Normal file
|
@ -0,0 +1,18 @@
|
|||
// Copyright 2019 The Go Authors. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
package unix
|
||||
|
||||
import "unsafe"
|
||||
|
||||
// FcntlInt performs a fcntl syscall on fd with the provided command and argument.
|
||||
func FcntlInt(fd uintptr, cmd, arg int) (int, error) {
|
||||
return fcntl(int(fd), cmd, arg)
|
||||
}
|
||||
|
||||
// FcntlFlock performs a fcntl syscall for the F_GETLK, F_SETLK or F_SETLKW command.
|
||||
func FcntlFlock(fd uintptr, cmd int, lk *Flock_t) error {
|
||||
_, err := fcntl(int(fd), cmd, int(uintptr(unsafe.Pointer(lk))))
|
||||
return err
|
||||
}
|
20
vendor/golang.org/x/sys/unix/mkall.sh
generated
vendored
20
vendor/golang.org/x/sys/unix/mkall.sh
generated
vendored
|
@ -62,12 +62,12 @@ _* | *_ | _)
|
|||
;;
|
||||
aix_ppc)
|
||||
mkerrors="$mkerrors -maix32"
|
||||
mksyscall="./mksyscall_aix_ppc.pl -aix"
|
||||
mksyscall="go run mksyscall_aix_ppc.go -aix"
|
||||
mktypes="GOARCH=$GOARCH go tool cgo -godefs"
|
||||
;;
|
||||
aix_ppc64)
|
||||
mkerrors="$mkerrors -maix64"
|
||||
mksyscall="./mksyscall_aix_ppc64.pl -aix"
|
||||
mksyscall="go run mksyscall_aix_ppc64.go -aix"
|
||||
mktypes="GOARCH=$GOARCH go tool cgo -godefs"
|
||||
;;
|
||||
darwin_386)
|
||||
|
@ -99,31 +99,31 @@ darwin_arm64)
|
|||
dragonfly_amd64)
|
||||
mkerrors="$mkerrors -m64"
|
||||
mksyscall="go run mksyscall.go -dragonfly"
|
||||
mksysnum="go run mksysnum.go 'http://gitweb.dragonflybsd.org/dragonfly.git/blob_plain/HEAD:/sys/kern/syscalls.master'"
|
||||
mksysnum="go run mksysnum.go 'https://gitweb.dragonflybsd.org/dragonfly.git/blob_plain/HEAD:/sys/kern/syscalls.master'"
|
||||
mktypes="GOARCH=$GOARCH go tool cgo -godefs"
|
||||
;;
|
||||
freebsd_386)
|
||||
mkerrors="$mkerrors -m32"
|
||||
mksyscall="go run mksyscall.go -l32"
|
||||
mksysnum="go run mksysnum.go 'http://svn.freebsd.org/base/stable/10/sys/kern/syscalls.master'"
|
||||
mksysnum="go run mksysnum.go 'https://svn.freebsd.org/base/stable/10/sys/kern/syscalls.master'"
|
||||
mktypes="GOARCH=$GOARCH go tool cgo -godefs"
|
||||
;;
|
||||
freebsd_amd64)
|
||||
mkerrors="$mkerrors -m64"
|
||||
mksysnum="go run mksysnum.go 'http://svn.freebsd.org/base/stable/10/sys/kern/syscalls.master'"
|
||||
mksysnum="go run mksysnum.go 'https://svn.freebsd.org/base/stable/10/sys/kern/syscalls.master'"
|
||||
mktypes="GOARCH=$GOARCH go tool cgo -godefs"
|
||||
;;
|
||||
freebsd_arm)
|
||||
mkerrors="$mkerrors"
|
||||
mksyscall="go run mksyscall.go -l32 -arm"
|
||||
mksysnum="go run mksysnum.go 'http://svn.freebsd.org/base/stable/10/sys/kern/syscalls.master'"
|
||||
mksysnum="go run mksysnum.go 'https://svn.freebsd.org/base/stable/10/sys/kern/syscalls.master'"
|
||||
# Let the type of C char be signed for making the bare syscall
|
||||
# API consistent across platforms.
|
||||
mktypes="GOARCH=$GOARCH go tool cgo -godefs -- -fsigned-char"
|
||||
;;
|
||||
freebsd_arm64)
|
||||
mkerrors="$mkerrors -m64"
|
||||
mksysnum="go run mksysnum.go 'http://svn.freebsd.org/base/stable/10/sys/kern/syscalls.master'"
|
||||
mksysnum="go run mksysnum.go 'https://svn.freebsd.org/base/stable/10/sys/kern/syscalls.master'"
|
||||
mktypes="GOARCH=$GOARCH go tool cgo -godefs"
|
||||
;;
|
||||
netbsd_386)
|
||||
|
@ -150,21 +150,21 @@ openbsd_386)
|
|||
mkerrors="$mkerrors -m32"
|
||||
mksyscall="go run mksyscall.go -l32 -openbsd"
|
||||
mksysctl="./mksysctl_openbsd.pl"
|
||||
mksysnum="go run mksysnum.go 'http://cvsweb.openbsd.org/cgi-bin/cvsweb/~checkout~/src/sys/kern/syscalls.master'"
|
||||
mksysnum="go run mksysnum.go 'https://cvsweb.openbsd.org/cgi-bin/cvsweb/~checkout~/src/sys/kern/syscalls.master'"
|
||||
mktypes="GOARCH=$GOARCH go tool cgo -godefs"
|
||||
;;
|
||||
openbsd_amd64)
|
||||
mkerrors="$mkerrors -m64"
|
||||
mksyscall="go run mksyscall.go -openbsd"
|
||||
mksysctl="./mksysctl_openbsd.pl"
|
||||
mksysnum="go run mksysnum.go 'http://cvsweb.openbsd.org/cgi-bin/cvsweb/~checkout~/src/sys/kern/syscalls.master'"
|
||||
mksysnum="go run mksysnum.go 'https://cvsweb.openbsd.org/cgi-bin/cvsweb/~checkout~/src/sys/kern/syscalls.master'"
|
||||
mktypes="GOARCH=$GOARCH go tool cgo -godefs"
|
||||
;;
|
||||
openbsd_arm)
|
||||
mkerrors="$mkerrors"
|
||||
mksyscall="go run mksyscall.go -l32 -openbsd -arm"
|
||||
mksysctl="./mksysctl_openbsd.pl"
|
||||
mksysnum="go run mksysnum.go 'http://cvsweb.openbsd.org/cgi-bin/cvsweb/~checkout~/src/sys/kern/syscalls.master'"
|
||||
mksysnum="go run mksysnum.go 'https://cvsweb.openbsd.org/cgi-bin/cvsweb/~checkout~/src/sys/kern/syscalls.master'"
|
||||
# Let the type of C char be signed for making the bare syscall
|
||||
# API consistent across platforms.
|
||||
mktypes="GOARCH=$GOARCH go tool cgo -godefs -- -fsigned-char"
|
||||
|
|
7
vendor/golang.org/x/sys/unix/mkerrors.sh
generated
vendored
7
vendor/golang.org/x/sys/unix/mkerrors.sh
generated
vendored
|
@ -179,8 +179,10 @@ struct ltchars {
|
|||
#include <sys/stat.h>
|
||||
#include <sys/types.h>
|
||||
#include <sys/time.h>
|
||||
#include <sys/signalfd.h>
|
||||
#include <sys/socket.h>
|
||||
#include <sys/xattr.h>
|
||||
#include <linux/errqueue.h>
|
||||
#include <linux/if.h>
|
||||
#include <linux/if_alg.h>
|
||||
#include <linux/if_arp.h>
|
||||
|
@ -453,7 +455,7 @@ ccflags="$@"
|
|||
$2 !~ "MNT_BITS" &&
|
||||
$2 ~ /^(MS|MNT|UMOUNT)_/ ||
|
||||
$2 ~ /^TUN(SET|GET|ATTACH|DETACH)/ ||
|
||||
$2 ~ /^(O|F|E?FD|NAME|S|PTRACE|PT)_/ ||
|
||||
$2 ~ /^(O|F|[ES]?FD|NAME|S|PTRACE|PT)_/ ||
|
||||
$2 ~ /^KEXEC_/ ||
|
||||
$2 ~ /^LINUX_REBOOT_CMD_/ ||
|
||||
$2 ~ /^LINUX_REBOOT_MAGIC[12]$/ ||
|
||||
|
@ -474,12 +476,13 @@ ccflags="$@"
|
|||
$2 ~ /^CLONE_[A-Z_]+/ ||
|
||||
$2 !~ /^(BPF_TIMEVAL)$/ &&
|
||||
$2 ~ /^(BPF|DLT)_/ ||
|
||||
$2 ~ /^CLOCK_/ ||
|
||||
$2 ~ /^(CLOCK|TIMER)_/ ||
|
||||
$2 ~ /^CAN_/ ||
|
||||
$2 ~ /^CAP_/ ||
|
||||
$2 ~ /^ALG_/ ||
|
||||
$2 ~ /^FS_(POLICY_FLAGS|KEY_DESC|ENCRYPTION_MODE|[A-Z0-9_]+_KEY_SIZE|IOC_(GET|SET)_ENCRYPTION)/ ||
|
||||
$2 ~ /^GRND_/ ||
|
||||
$2 ~ /^RND/ ||
|
||||
$2 ~ /^KEY_(SPEC|REQKEY_DEFL)_/ ||
|
||||
$2 ~ /^KEYCTL_/ ||
|
||||
$2 ~ /^PERF_EVENT_IOC_/ ||
|
||||
|
|
404
vendor/golang.org/x/sys/unix/mksyscall_aix_ppc.go
generated
vendored
Normal file
404
vendor/golang.org/x/sys/unix/mksyscall_aix_ppc.go
generated
vendored
Normal file
|
@ -0,0 +1,404 @@
|
|||
// Copyright 2019 The Go Authors. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
// +build ignore
|
||||
|
||||
/*
|
||||
This program reads a file containing function prototypes
|
||||
(like syscall_aix.go) and generates system call bodies.
|
||||
The prototypes are marked by lines beginning with "//sys"
|
||||
and read like func declarations if //sys is replaced by func, but:
|
||||
* The parameter lists must give a name for each argument.
|
||||
This includes return parameters.
|
||||
* The parameter lists must give a type for each argument:
|
||||
the (x, y, z int) shorthand is not allowed.
|
||||
* If the return parameter is an error number, it must be named err.
|
||||
* If go func name needs to be different than its libc name,
|
||||
* or the function is not in libc, name could be specified
|
||||
* at the end, after "=" sign, like
|
||||
//sys getsockopt(s int, level int, name int, val uintptr, vallen *_Socklen) (err error) = libsocket.getsockopt
|
||||
*/
|
||||
package main
|
||||
|
||||
import (
|
||||
"bufio"
|
||||
"flag"
|
||||
"fmt"
|
||||
"os"
|
||||
"regexp"
|
||||
"strings"
|
||||
)
|
||||
|
||||
var (
|
||||
b32 = flag.Bool("b32", false, "32bit big-endian")
|
||||
l32 = flag.Bool("l32", false, "32bit little-endian")
|
||||
aix = flag.Bool("aix", false, "aix")
|
||||
tags = flag.String("tags", "", "build tags")
|
||||
)
|
||||
|
||||
// cmdLine returns this programs's commandline arguments
|
||||
func cmdLine() string {
|
||||
return "go run mksyscall_aix_ppc.go " + strings.Join(os.Args[1:], " ")
|
||||
}
|
||||
|
||||
// buildTags returns build tags
|
||||
func buildTags() string {
|
||||
return *tags
|
||||
}
|
||||
|
||||
// Param is function parameter
|
||||
type Param struct {
|
||||
Name string
|
||||
Type string
|
||||
}
|
||||
|
||||
// usage prints the program usage
|
||||
func usage() {
|
||||
fmt.Fprintf(os.Stderr, "usage: go run mksyscall_aix_ppc.go [-b32 | -l32] [-tags x,y] [file ...]\n")
|
||||
os.Exit(1)
|
||||
}
|
||||
|
||||
// parseParamList parses parameter list and returns a slice of parameters
|
||||
func parseParamList(list string) []string {
|
||||
list = strings.TrimSpace(list)
|
||||
if list == "" {
|
||||
return []string{}
|
||||
}
|
||||
return regexp.MustCompile(`\s*,\s*`).Split(list, -1)
|
||||
}
|
||||
|
||||
// parseParam splits a parameter into name and type
|
||||
func parseParam(p string) Param {
|
||||
ps := regexp.MustCompile(`^(\S*) (\S*)$`).FindStringSubmatch(p)
|
||||
if ps == nil {
|
||||
fmt.Fprintf(os.Stderr, "malformed parameter: %s\n", p)
|
||||
os.Exit(1)
|
||||
}
|
||||
return Param{ps[1], ps[2]}
|
||||
}
|
||||
|
||||
func main() {
|
||||
flag.Usage = usage
|
||||
flag.Parse()
|
||||
if len(flag.Args()) <= 0 {
|
||||
fmt.Fprintf(os.Stderr, "no files to parse provided\n")
|
||||
usage()
|
||||
}
|
||||
|
||||
endianness := ""
|
||||
if *b32 {
|
||||
endianness = "big-endian"
|
||||
} else if *l32 {
|
||||
endianness = "little-endian"
|
||||
}
|
||||
|
||||
pack := ""
|
||||
text := ""
|
||||
cExtern := "/*\n#include <stdint.h>\n#include <stddef.h>\n"
|
||||
for _, path := range flag.Args() {
|
||||
file, err := os.Open(path)
|
||||
if err != nil {
|
||||
fmt.Fprintf(os.Stderr, err.Error())
|
||||
os.Exit(1)
|
||||
}
|
||||
s := bufio.NewScanner(file)
|
||||
for s.Scan() {
|
||||
t := s.Text()
|
||||
t = strings.TrimSpace(t)
|
||||
t = regexp.MustCompile(`\s+`).ReplaceAllString(t, ` `)
|
||||
if p := regexp.MustCompile(`^package (\S+)$`).FindStringSubmatch(t); p != nil && pack == "" {
|
||||
pack = p[1]
|
||||
}
|
||||
nonblock := regexp.MustCompile(`^\/\/sysnb `).FindStringSubmatch(t)
|
||||
if regexp.MustCompile(`^\/\/sys `).FindStringSubmatch(t) == nil && nonblock == nil {
|
||||
continue
|
||||
}
|
||||
|
||||
// Line must be of the form
|
||||
// func Open(path string, mode int, perm int) (fd int, err error)
|
||||
// Split into name, in params, out params.
|
||||
f := regexp.MustCompile(`^\/\/sys(nb)? (\w+)\(([^()]*)\)\s*(?:\(([^()]+)\))?\s*(?:=\s*(?:(\w*)\.)?(\w*))?$`).FindStringSubmatch(t)
|
||||
if f == nil {
|
||||
fmt.Fprintf(os.Stderr, "%s:%s\nmalformed //sys declaration\n", path, t)
|
||||
os.Exit(1)
|
||||
}
|
||||
funct, inps, outps, modname, sysname := f[2], f[3], f[4], f[5], f[6]
|
||||
|
||||
// Split argument lists on comma.
|
||||
in := parseParamList(inps)
|
||||
out := parseParamList(outps)
|
||||
|
||||
inps = strings.Join(in, ", ")
|
||||
outps = strings.Join(out, ", ")
|
||||
|
||||
// Try in vain to keep people from editing this file.
|
||||
// The theory is that they jump into the middle of the file
|
||||
// without reading the header.
|
||||
text += "// THIS FILE IS GENERATED BY THE COMMAND AT THE TOP; DO NOT EDIT\n\n"
|
||||
|
||||
// Check if value return, err return available
|
||||
errvar := ""
|
||||
retvar := ""
|
||||
rettype := ""
|
||||
for _, param := range out {
|
||||
p := parseParam(param)
|
||||
if p.Type == "error" {
|
||||
errvar = p.Name
|
||||
} else {
|
||||
retvar = p.Name
|
||||
rettype = p.Type
|
||||
}
|
||||
}
|
||||
|
||||
// System call name.
|
||||
if sysname == "" {
|
||||
sysname = funct
|
||||
}
|
||||
sysname = regexp.MustCompile(`([a-z])([A-Z])`).ReplaceAllString(sysname, `${1}_$2`)
|
||||
sysname = strings.ToLower(sysname) // All libc functions are lowercase.
|
||||
|
||||
cRettype := ""
|
||||
if rettype == "unsafe.Pointer" {
|
||||
cRettype = "uintptr_t"
|
||||
} else if rettype == "uintptr" {
|
||||
cRettype = "uintptr_t"
|
||||
} else if regexp.MustCompile(`^_`).FindStringSubmatch(rettype) != nil {
|
||||
cRettype = "uintptr_t"
|
||||
} else if rettype == "int" {
|
||||
cRettype = "int"
|
||||
} else if rettype == "int32" {
|
||||
cRettype = "int"
|
||||
} else if rettype == "int64" {
|
||||
cRettype = "long long"
|
||||
} else if rettype == "uint32" {
|
||||
cRettype = "unsigned int"
|
||||
} else if rettype == "uint64" {
|
||||
cRettype = "unsigned long long"
|
||||
} else {
|
||||
cRettype = "int"
|
||||
}
|
||||
if sysname == "exit" {
|
||||
cRettype = "void"
|
||||
}
|
||||
|
||||
// Change p.Types to c
|
||||
var cIn []string
|
||||
for _, param := range in {
|
||||
p := parseParam(param)
|
||||
if regexp.MustCompile(`^\*`).FindStringSubmatch(p.Type) != nil {
|
||||
cIn = append(cIn, "uintptr_t")
|
||||
} else if p.Type == "string" {
|
||||
cIn = append(cIn, "uintptr_t")
|
||||
} else if regexp.MustCompile(`^\[\](.*)`).FindStringSubmatch(p.Type) != nil {
|
||||
cIn = append(cIn, "uintptr_t", "size_t")
|
||||
} else if p.Type == "unsafe.Pointer" {
|
||||
cIn = append(cIn, "uintptr_t")
|
||||
} else if p.Type == "uintptr" {
|
||||
cIn = append(cIn, "uintptr_t")
|
||||
} else if regexp.MustCompile(`^_`).FindStringSubmatch(p.Type) != nil {
|
||||
cIn = append(cIn, "uintptr_t")
|
||||
} else if p.Type == "int" {
|
||||
cIn = append(cIn, "int")
|
||||
} else if p.Type == "int32" {
|
||||
cIn = append(cIn, "int")
|
||||
} else if p.Type == "int64" {
|
||||
cIn = append(cIn, "long long")
|
||||
} else if p.Type == "uint32" {
|
||||
cIn = append(cIn, "unsigned int")
|
||||
} else if p.Type == "uint64" {
|
||||
cIn = append(cIn, "unsigned long long")
|
||||
} else {
|
||||
cIn = append(cIn, "int")
|
||||
}
|
||||
}
|
||||
|
||||
if funct != "fcntl" && funct != "FcntlInt" && funct != "readlen" && funct != "writelen" {
|
||||
// Imports of system calls from libc
|
||||
cExtern += fmt.Sprintf("%s %s", cRettype, sysname)
|
||||
cIn := strings.Join(cIn, ", ")
|
||||
cExtern += fmt.Sprintf("(%s);\n", cIn)
|
||||
}
|
||||
|
||||
// So file name.
|
||||
if *aix {
|
||||
if modname == "" {
|
||||
modname = "libc.a/shr_64.o"
|
||||
} else {
|
||||
fmt.Fprintf(os.Stderr, "%s: only syscall using libc are available\n", funct)
|
||||
os.Exit(1)
|
||||
}
|
||||
}
|
||||
|
||||
strconvfunc := "C.CString"
|
||||
|
||||
// Go function header.
|
||||
if outps != "" {
|
||||
outps = fmt.Sprintf(" (%s)", outps)
|
||||
}
|
||||
if text != "" {
|
||||
text += "\n"
|
||||
}
|
||||
|
||||
text += fmt.Sprintf("func %s(%s)%s {\n", funct, strings.Join(in, ", "), outps)
|
||||
|
||||
// Prepare arguments to Syscall.
|
||||
var args []string
|
||||
n := 0
|
||||
argN := 0
|
||||
for _, param := range in {
|
||||
p := parseParam(param)
|
||||
if regexp.MustCompile(`^\*`).FindStringSubmatch(p.Type) != nil {
|
||||
args = append(args, "C.uintptr_t(uintptr(unsafe.Pointer("+p.Name+")))")
|
||||
} else if p.Type == "string" && errvar != "" {
|
||||
text += fmt.Sprintf("\t_p%d := uintptr(unsafe.Pointer(%s(%s)))\n", n, strconvfunc, p.Name)
|
||||
args = append(args, fmt.Sprintf("C.uintptr_t(_p%d)", n))
|
||||
n++
|
||||
} else if p.Type == "string" {
|
||||
fmt.Fprintf(os.Stderr, path+":"+funct+" uses string arguments, but has no error return\n")
|
||||
text += fmt.Sprintf("\t_p%d := uintptr(unsafe.Pointer(%s(%s)))\n", n, strconvfunc, p.Name)
|
||||
args = append(args, fmt.Sprintf("C.uintptr_t(_p%d)", n))
|
||||
n++
|
||||
} else if m := regexp.MustCompile(`^\[\](.*)`).FindStringSubmatch(p.Type); m != nil {
|
||||
// Convert slice into pointer, length.
|
||||
// Have to be careful not to take address of &a[0] if len == 0:
|
||||
// pass nil in that case.
|
||||
text += fmt.Sprintf("\tvar _p%d *%s\n", n, m[1])
|
||||
text += fmt.Sprintf("\tif len(%s) > 0 {\n\t\t_p%d = &%s[0]\n\t}\n", p.Name, n, p.Name)
|
||||
args = append(args, fmt.Sprintf("C.uintptr_t(uintptr(unsafe.Pointer(_p%d)))", n))
|
||||
n++
|
||||
text += fmt.Sprintf("\tvar _p%d int\n", n)
|
||||
text += fmt.Sprintf("\t_p%d = len(%s)\n", n, p.Name)
|
||||
args = append(args, fmt.Sprintf("C.size_t(_p%d)", n))
|
||||
n++
|
||||
} else if p.Type == "int64" && endianness != "" {
|
||||
if endianness == "big-endian" {
|
||||
args = append(args, fmt.Sprintf("uintptr(%s>>32)", p.Name), fmt.Sprintf("uintptr(%s)", p.Name))
|
||||
} else {
|
||||
args = append(args, fmt.Sprintf("uintptr(%s)", p.Name), fmt.Sprintf("uintptr(%s>>32)", p.Name))
|
||||
}
|
||||
n++
|
||||
} else if p.Type == "bool" {
|
||||
text += fmt.Sprintf("\tvar _p%d uint32\n", n)
|
||||
text += fmt.Sprintf("\tif %s {\n\t\t_p%d = 1\n\t} else {\n\t\t_p%d = 0\n\t}\n", p.Name, n, n)
|
||||
args = append(args, fmt.Sprintf("_p%d", n))
|
||||
} else if regexp.MustCompile(`^_`).FindStringSubmatch(p.Type) != nil {
|
||||
args = append(args, fmt.Sprintf("C.uintptr_t(uintptr(%s))", p.Name))
|
||||
} else if p.Type == "unsafe.Pointer" {
|
||||
args = append(args, fmt.Sprintf("C.uintptr_t(uintptr(%s))", p.Name))
|
||||
} else if p.Type == "int" {
|
||||
if (argN == 2) && ((funct == "readlen") || (funct == "writelen")) {
|
||||
args = append(args, fmt.Sprintf("C.size_t(%s)", p.Name))
|
||||
} else if argN == 0 && funct == "fcntl" {
|
||||
args = append(args, fmt.Sprintf("C.uintptr_t(%s)", p.Name))
|
||||
} else if (argN == 2) && ((funct == "fcntl") || (funct == "FcntlInt")) {
|
||||
args = append(args, fmt.Sprintf("C.uintptr_t(%s)", p.Name))
|
||||
} else {
|
||||
args = append(args, fmt.Sprintf("C.int(%s)", p.Name))
|
||||
}
|
||||
} else if p.Type == "int32" {
|
||||
args = append(args, fmt.Sprintf("C.int(%s)", p.Name))
|
||||
} else if p.Type == "int64" {
|
||||
args = append(args, fmt.Sprintf("C.longlong(%s)", p.Name))
|
||||
} else if p.Type == "uint32" {
|
||||
args = append(args, fmt.Sprintf("C.uint(%s)", p.Name))
|
||||
} else if p.Type == "uint64" {
|
||||
args = append(args, fmt.Sprintf("C.ulonglong(%s)", p.Name))
|
||||
} else if p.Type == "uintptr" {
|
||||
args = append(args, fmt.Sprintf("C.uintptr_t(%s)", p.Name))
|
||||
} else {
|
||||
args = append(args, fmt.Sprintf("C.int(%s)", p.Name))
|
||||
}
|
||||
argN++
|
||||
}
|
||||
|
||||
// Actual call.
|
||||
arglist := strings.Join(args, ", ")
|
||||
call := ""
|
||||
if sysname == "exit" {
|
||||
if errvar != "" {
|
||||
call += "er :="
|
||||
} else {
|
||||
call += ""
|
||||
}
|
||||
} else if errvar != "" {
|
||||
call += "r0,er :="
|
||||
} else if retvar != "" {
|
||||
call += "r0,_ :="
|
||||
} else {
|
||||
call += ""
|
||||
}
|
||||
call += fmt.Sprintf("C.%s(%s)", sysname, arglist)
|
||||
|
||||
// Assign return values.
|
||||
body := ""
|
||||
for i := 0; i < len(out); i++ {
|
||||
p := parseParam(out[i])
|
||||
reg := ""
|
||||
if p.Name == "err" {
|
||||
reg = "e1"
|
||||
} else {
|
||||
reg = "r0"
|
||||
}
|
||||
if reg != "e1" {
|
||||
body += fmt.Sprintf("\t%s = %s(%s)\n", p.Name, p.Type, reg)
|
||||
}
|
||||
}
|
||||
|
||||
// verify return
|
||||
if sysname != "exit" && errvar != "" {
|
||||
if regexp.MustCompile(`^uintptr`).FindStringSubmatch(cRettype) != nil {
|
||||
body += "\tif (uintptr(r0) ==^uintptr(0) && er != nil) {\n"
|
||||
body += fmt.Sprintf("\t\t%s = er\n", errvar)
|
||||
body += "\t}\n"
|
||||
} else {
|
||||
body += "\tif (r0 ==-1 && er != nil) {\n"
|
||||
body += fmt.Sprintf("\t\t%s = er\n", errvar)
|
||||
body += "\t}\n"
|
||||
}
|
||||
} else if errvar != "" {
|
||||
body += "\tif (er != nil) {\n"
|
||||
body += fmt.Sprintf("\t\t%s = er\n", errvar)
|
||||
body += "\t}\n"
|
||||
}
|
||||
|
||||
text += fmt.Sprintf("\t%s\n", call)
|
||||
text += body
|
||||
|
||||
text += "\treturn\n"
|
||||
text += "}\n"
|
||||
}
|
||||
if err := s.Err(); err != nil {
|
||||
fmt.Fprintf(os.Stderr, err.Error())
|
||||
os.Exit(1)
|
||||
}
|
||||
file.Close()
|
||||
}
|
||||
imp := ""
|
||||
if pack != "unix" {
|
||||
imp = "import \"golang.org/x/sys/unix\"\n"
|
||||
|
||||
}
|
||||
fmt.Printf(srcTemplate, cmdLine(), buildTags(), pack, cExtern, imp, text)
|
||||
}
|
||||
|
||||
const srcTemplate = `// %s
|
||||
// Code generated by the command above; see README.md. DO NOT EDIT.
|
||||
|
||||
// +build %s
|
||||
|
||||
package %s
|
||||
|
||||
|
||||
%s
|
||||
*/
|
||||
import "C"
|
||||
import (
|
||||
"unsafe"
|
||||
)
|
||||
|
||||
|
||||
%s
|
||||
|
||||
%s
|
||||
`
|
384
vendor/golang.org/x/sys/unix/mksyscall_aix_ppc.pl
generated
vendored
384
vendor/golang.org/x/sys/unix/mksyscall_aix_ppc.pl
generated
vendored
|
@ -1,384 +0,0 @@
|
|||
#!/usr/bin/env perl
|
||||
# Copyright 2018 The Go Authors. All rights reserved.
|
||||
# Use of this source code is governed by a BSD-style
|
||||
# license that can be found in the LICENSE file.
|
||||
|
||||
# This program reads a file containing function prototypes
|
||||
# (like syscall_aix.go) and generates system call bodies.
|
||||
# The prototypes are marked by lines beginning with "//sys"
|
||||
# and read like func declarations if //sys is replaced by func, but:
|
||||
# * The parameter lists must give a name for each argument.
|
||||
# This includes return parameters.
|
||||
# * The parameter lists must give a type for each argument:
|
||||
# the (x, y, z int) shorthand is not allowed.
|
||||
# * If the return parameter is an error number, it must be named err.
|
||||
# * If go func name needs to be different than its libc name,
|
||||
# * or the function is not in libc, name could be specified
|
||||
# * at the end, after "=" sign, like
|
||||
# //sys getsockopt(s int, level int, name int, val uintptr, vallen *_Socklen) (err error) = libsocket.getsockopt
|
||||
|
||||
use strict;
|
||||
|
||||
my $cmdline = "mksyscall_aix_ppc.pl " . join(' ', @ARGV);
|
||||
my $errors = 0;
|
||||
my $_32bit = "";
|
||||
my $tags = ""; # build tags
|
||||
my $aix = 0;
|
||||
my $solaris = 0;
|
||||
|
||||
binmode STDOUT;
|
||||
|
||||
if($ARGV[0] eq "-b32") {
|
||||
$_32bit = "big-endian";
|
||||
shift;
|
||||
} elsif($ARGV[0] eq "-l32") {
|
||||
$_32bit = "little-endian";
|
||||
shift;
|
||||
}
|
||||
if($ARGV[0] eq "-aix") {
|
||||
$aix = 1;
|
||||
shift;
|
||||
}
|
||||
if($ARGV[0] eq "-tags") {
|
||||
shift;
|
||||
$tags = $ARGV[0];
|
||||
shift;
|
||||
}
|
||||
|
||||
if($ARGV[0] =~ /^-/) {
|
||||
print STDERR "usage: mksyscall_aix.pl [-b32 | -l32] [-tags x,y] [file ...]\n";
|
||||
exit 1;
|
||||
}
|
||||
|
||||
sub parseparamlist($) {
|
||||
my ($list) = @_;
|
||||
$list =~ s/^\s*//;
|
||||
$list =~ s/\s*$//;
|
||||
if($list eq "") {
|
||||
return ();
|
||||
}
|
||||
return split(/\s*,\s*/, $list);
|
||||
}
|
||||
|
||||
sub parseparam($) {
|
||||
my ($p) = @_;
|
||||
if($p !~ /^(\S*) (\S*)$/) {
|
||||
print STDERR "$ARGV:$.: malformed parameter: $p\n";
|
||||
$errors = 1;
|
||||
return ("xx", "int");
|
||||
}
|
||||
return ($1, $2);
|
||||
}
|
||||
|
||||
my $package = "";
|
||||
my $text = "";
|
||||
my $c_extern = "/*\n#include <stdint.h>\n#include <stddef.h>\n";
|
||||
my @vars = ();
|
||||
while(<>) {
|
||||
chomp;
|
||||
s/\s+/ /g;
|
||||
s/^\s+//;
|
||||
s/\s+$//;
|
||||
$package = $1 if !$package && /^package (\S+)$/;
|
||||
my $nonblock = /^\/\/sysnb /;
|
||||
next if !/^\/\/sys / && !$nonblock;
|
||||
|
||||
# Line must be of the form
|
||||
# func Open(path string, mode int, perm int) (fd int, err error)
|
||||
# Split into name, in params, out params.
|
||||
if(!/^\/\/sys(nb)? (\w+)\(([^()]*)\)\s*(?:\(([^()]+)\))?\s*(?:=\s*(?:(\w*)\.)?(\w*))?$/) {
|
||||
print STDERR "$ARGV:$.: malformed //sys declaration\n";
|
||||
$errors = 1;
|
||||
next;
|
||||
}
|
||||
my ($nb, $func, $in, $out, $modname, $sysname) = ($1, $2, $3, $4, $5, $6);
|
||||
|
||||
# Split argument lists on comma.
|
||||
my @in = parseparamlist($in);
|
||||
my @out = parseparamlist($out);
|
||||
|
||||
$in = join(', ', @in);
|
||||
$out = join(', ', @out);
|
||||
|
||||
# Try in vain to keep people from editing this file.
|
||||
# The theory is that they jump into the middle of the file
|
||||
# without reading the header.
|
||||
$text .= "// THIS FILE IS GENERATED BY THE COMMAND AT THE TOP; DO NOT EDIT\n\n";
|
||||
|
||||
# Check if value return, err return available
|
||||
my $errvar = "";
|
||||
my $retvar = "";
|
||||
my $rettype = "";
|
||||
foreach my $p (@out) {
|
||||
my ($name, $type) = parseparam($p);
|
||||
if($type eq "error") {
|
||||
$errvar = $name;
|
||||
} else {
|
||||
$retvar = $name;
|
||||
$rettype = $type;
|
||||
}
|
||||
}
|
||||
|
||||
# System call name.
|
||||
#if($func ne "fcntl") {
|
||||
|
||||
if($sysname eq "") {
|
||||
$sysname = "$func";
|
||||
}
|
||||
|
||||
$sysname =~ s/([a-z])([A-Z])/${1}_$2/g;
|
||||
$sysname =~ y/A-Z/a-z/; # All libc functions are lowercase.
|
||||
|
||||
my $C_rettype = "";
|
||||
if($rettype eq "unsafe.Pointer") {
|
||||
$C_rettype = "uintptr_t";
|
||||
} elsif($rettype eq "uintptr") {
|
||||
$C_rettype = "uintptr_t";
|
||||
} elsif($rettype =~ /^_/) {
|
||||
$C_rettype = "uintptr_t";
|
||||
} elsif($rettype eq "int") {
|
||||
$C_rettype = "int";
|
||||
} elsif($rettype eq "int32") {
|
||||
$C_rettype = "int";
|
||||
} elsif($rettype eq "int64") {
|
||||
$C_rettype = "long long";
|
||||
} elsif($rettype eq "uint32") {
|
||||
$C_rettype = "unsigned int";
|
||||
} elsif($rettype eq "uint64") {
|
||||
$C_rettype = "unsigned long long";
|
||||
} else {
|
||||
$C_rettype = "int";
|
||||
}
|
||||
if($sysname eq "exit") {
|
||||
$C_rettype = "void";
|
||||
}
|
||||
|
||||
# Change types to c
|
||||
my @c_in = ();
|
||||
foreach my $p (@in) {
|
||||
my ($name, $type) = parseparam($p);
|
||||
if($type =~ /^\*/) {
|
||||
push @c_in, "uintptr_t";
|
||||
} elsif($type eq "string") {
|
||||
push @c_in, "uintptr_t";
|
||||
} elsif($type =~ /^\[\](.*)/) {
|
||||
push @c_in, "uintptr_t", "size_t";
|
||||
} elsif($type eq "unsafe.Pointer") {
|
||||
push @c_in, "uintptr_t";
|
||||
} elsif($type eq "uintptr") {
|
||||
push @c_in, "uintptr_t";
|
||||
} elsif($type =~ /^_/) {
|
||||
push @c_in, "uintptr_t";
|
||||
} elsif($type eq "int") {
|
||||
push @c_in, "int";
|
||||
} elsif($type eq "int32") {
|
||||
push @c_in, "int";
|
||||
} elsif($type eq "int64") {
|
||||
push @c_in, "long long";
|
||||
} elsif($type eq "uint32") {
|
||||
push @c_in, "unsigned int";
|
||||
} elsif($type eq "uint64") {
|
||||
push @c_in, "unsigned long long";
|
||||
} else {
|
||||
push @c_in, "int";
|
||||
}
|
||||
}
|
||||
|
||||
if ($func ne "fcntl" && $func ne "FcntlInt" && $func ne "readlen" && $func ne "writelen") {
|
||||
# Imports of system calls from libc
|
||||
$c_extern .= "$C_rettype $sysname";
|
||||
my $c_in = join(', ', @c_in);
|
||||
$c_extern .= "($c_in);\n";
|
||||
}
|
||||
|
||||
# So file name.
|
||||
if($aix) {
|
||||
if($modname eq "") {
|
||||
$modname = "libc.a/shr_64.o";
|
||||
} else {
|
||||
print STDERR "$func: only syscall using libc are available\n";
|
||||
$errors = 1;
|
||||
next;
|
||||
}
|
||||
}
|
||||
|
||||
my $strconvfunc = "C.CString";
|
||||
my $strconvtype = "*byte";
|
||||
|
||||
# Go function header.
|
||||
if($out ne "") {
|
||||
$out = " ($out)";
|
||||
}
|
||||
if($text ne "") {
|
||||
$text .= "\n"
|
||||
}
|
||||
|
||||
$text .= sprintf "func %s(%s)%s {\n", $func, join(', ', @in), $out ;
|
||||
|
||||
# Prepare arguments to call.
|
||||
my @args = ();
|
||||
my $n = 0;
|
||||
my $arg_n = 0;
|
||||
foreach my $p (@in) {
|
||||
my ($name, $type) = parseparam($p);
|
||||
if($type =~ /^\*/) {
|
||||
push @args, "C.uintptr_t(uintptr(unsafe.Pointer($name)))";
|
||||
} elsif($type eq "string" && $errvar ne "") {
|
||||
$text .= "\t_p$n := uintptr(unsafe.Pointer($strconvfunc($name)))\n";
|
||||
push @args, "C.uintptr_t(_p$n)";
|
||||
$n++;
|
||||
} elsif($type eq "string") {
|
||||
print STDERR "$ARGV:$.: $func uses string arguments, but has no error return\n";
|
||||
$text .= "\t_p$n := uintptr(unsafe.Pointer($strconvfunc($name)))\n";
|
||||
push @args, "C.uintptr_t(_p$n)";
|
||||
$n++;
|
||||
} elsif($type =~ /^\[\](.*)/) {
|
||||
# Convert slice into pointer, length.
|
||||
# Have to be careful not to take address of &a[0] if len == 0:
|
||||
# pass nil in that case.
|
||||
$text .= "\tvar _p$n *$1\n";
|
||||
$text .= "\tif len($name) > 0 {\n\t\t_p$n = \&$name\[0]\n\t}\n";
|
||||
push @args, "C.uintptr_t(uintptr(unsafe.Pointer(_p$n)))";
|
||||
$n++;
|
||||
$text .= "\tvar _p$n int\n";
|
||||
$text .= "\t_p$n = len($name)\n";
|
||||
push @args, "C.size_t(_p$n)";
|
||||
$n++;
|
||||
} elsif($type eq "int64" && $_32bit ne "") {
|
||||
if($_32bit eq "big-endian") {
|
||||
push @args, "uintptr($name >> 32)", "uintptr($name)";
|
||||
} else {
|
||||
push @args, "uintptr($name)", "uintptr($name >> 32)";
|
||||
}
|
||||
$n++;
|
||||
} elsif($type eq "bool") {
|
||||
$text .= "\tvar _p$n uint32\n";
|
||||
$text .= "\tif $name {\n\t\t_p$n = 1\n\t} else {\n\t\t_p$n = 0\n\t}\n";
|
||||
push @args, "_p$n";
|
||||
$n++;
|
||||
} elsif($type =~ /^_/) {
|
||||
push @args, "C.uintptr_t(uintptr($name))";
|
||||
} elsif($type eq "unsafe.Pointer") {
|
||||
push @args, "C.uintptr_t(uintptr($name))";
|
||||
} elsif($type eq "int") {
|
||||
if (($arg_n == 2) && (($func eq "readlen") || ($func eq "writelen"))) {
|
||||
push @args, "C.size_t($name)";
|
||||
} elsif ($arg_n == 0 && $func eq "fcntl") {
|
||||
push @args, "C.uintptr_t($name)";
|
||||
} elsif (($arg_n == 2) && (($func eq "fcntl") || ($func eq "FcntlInt"))) {
|
||||
push @args, "C.uintptr_t($name)";
|
||||
} else {
|
||||
push @args, "C.int($name)";
|
||||
}
|
||||
} elsif($type eq "int32") {
|
||||
push @args, "C.int($name)";
|
||||
} elsif($type eq "int64") {
|
||||
push @args, "C.longlong($name)";
|
||||
} elsif($type eq "uint32") {
|
||||
push @args, "C.uint($name)";
|
||||
} elsif($type eq "uint64") {
|
||||
push @args, "C.ulonglong($name)";
|
||||
} elsif($type eq "uintptr") {
|
||||
push @args, "C.uintptr_t($name)";
|
||||
} else {
|
||||
push @args, "C.int($name)";
|
||||
}
|
||||
$arg_n++;
|
||||
}
|
||||
my $nargs = @args;
|
||||
|
||||
|
||||
# Determine which form to use; pad args with zeros.
|
||||
if ($nonblock) {
|
||||
}
|
||||
|
||||
my $args = join(', ', @args);
|
||||
my $call = "";
|
||||
if ($sysname eq "exit") {
|
||||
if ($errvar ne "") {
|
||||
$call .= "er :=";
|
||||
} else {
|
||||
$call .= "";
|
||||
}
|
||||
} elsif ($errvar ne "") {
|
||||
$call .= "r0,er :=";
|
||||
} elsif ($retvar ne "") {
|
||||
$call .= "r0,_ :=";
|
||||
} else {
|
||||
$call .= ""
|
||||
}
|
||||
$call .= "C.$sysname($args)";
|
||||
|
||||
# Assign return values.
|
||||
my $body = "";
|
||||
my $failexpr = "";
|
||||
|
||||
for(my $i=0; $i<@out; $i++) {
|
||||
my $p = $out[$i];
|
||||
my ($name, $type) = parseparam($p);
|
||||
my $reg = "";
|
||||
if($name eq "err") {
|
||||
$reg = "e1";
|
||||
} else {
|
||||
$reg = "r0";
|
||||
}
|
||||
if($reg ne "e1" ) {
|
||||
$body .= "\t$name = $type($reg)\n";
|
||||
}
|
||||
}
|
||||
|
||||
# verify return
|
||||
if ($sysname ne "exit" && $errvar ne "") {
|
||||
if ($C_rettype =~ /^uintptr/) {
|
||||
$body .= "\tif \(uintptr\(r0\) ==\^uintptr\(0\) && er != nil\) {\n";
|
||||
$body .= "\t\t$errvar = er\n";
|
||||
$body .= "\t}\n";
|
||||
} else {
|
||||
$body .= "\tif \(r0 ==-1 && er != nil\) {\n";
|
||||
$body .= "\t\t$errvar = er\n";
|
||||
$body .= "\t}\n";
|
||||
}
|
||||
} elsif ($errvar ne "") {
|
||||
$body .= "\tif \(er != nil\) {\n";
|
||||
$body .= "\t\t$errvar = er\n";
|
||||
$body .= "\t}\n";
|
||||
}
|
||||
|
||||
$text .= "\t$call\n";
|
||||
$text .= $body;
|
||||
|
||||
$text .= "\treturn\n";
|
||||
$text .= "}\n";
|
||||
}
|
||||
|
||||
if($errors) {
|
||||
exit 1;
|
||||
}
|
||||
|
||||
print <<EOF;
|
||||
// $cmdline
|
||||
// Code generated by the command above; see README.md. DO NOT EDIT.
|
||||
|
||||
// +build $tags
|
||||
|
||||
package $package
|
||||
|
||||
|
||||
$c_extern
|
||||
*/
|
||||
import "C"
|
||||
import (
|
||||
"unsafe"
|
||||
)
|
||||
|
||||
|
||||
EOF
|
||||
|
||||
print "import \"golang.org/x/sys/unix\"\n" if $package ne "unix";
|
||||
|
||||
chomp($_=<<EOF);
|
||||
|
||||
$text
|
||||
EOF
|
||||
print $_;
|
||||
exit 0;
|
602
vendor/golang.org/x/sys/unix/mksyscall_aix_ppc64.go
generated
vendored
Normal file
602
vendor/golang.org/x/sys/unix/mksyscall_aix_ppc64.go
generated
vendored
Normal file
|
@ -0,0 +1,602 @@
|
|||
// Copyright 2019 The Go Authors. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
// +build ignore
|
||||
|
||||
/*
|
||||
This program reads a file containing function prototypes
|
||||
(like syscall_aix.go) and generates system call bodies.
|
||||
The prototypes are marked by lines beginning with "//sys"
|
||||
and read like func declarations if //sys is replaced by func, but:
|
||||
* The parameter lists must give a name for each argument.
|
||||
This includes return parameters.
|
||||
* The parameter lists must give a type for each argument:
|
||||
the (x, y, z int) shorthand is not allowed.
|
||||
* If the return parameter is an error number, it must be named err.
|
||||
* If go func name needs to be different than its libc name,
|
||||
* or the function is not in libc, name could be specified
|
||||
* at the end, after "=" sign, like
|
||||
//sys getsockopt(s int, level int, name int, val uintptr, vallen *_Socklen) (err error) = libsocket.getsockopt
|
||||
|
||||
|
||||
This program will generate three files and handle both gc and gccgo implementation:
|
||||
- zsyscall_aix_ppc64.go: the common part of each implementation (error handler, pointer creation)
|
||||
- zsyscall_aix_ppc64_gc.go: gc part with //go_cgo_import_dynamic and a call to syscall6
|
||||
- zsyscall_aix_ppc64_gccgo.go: gccgo part with C function and conversion to C type.
|
||||
|
||||
The generated code looks like this
|
||||
|
||||
zsyscall_aix_ppc64.go
|
||||
func asyscall(...) (n int, err error) {
|
||||
// Pointer Creation
|
||||
r1, e1 := callasyscall(...)
|
||||
// Type Conversion
|
||||
// Error Handler
|
||||
return
|
||||
}
|
||||
|
||||
zsyscall_aix_ppc64_gc.go
|
||||
//go:cgo_import_dynamic libc_asyscall asyscall "libc.a/shr_64.o"
|
||||
//go:linkname libc_asyscall libc_asyscall
|
||||
var asyscall syscallFunc
|
||||
|
||||
func callasyscall(...) (r1 uintptr, e1 Errno) {
|
||||
r1, _, e1 = syscall6(uintptr(unsafe.Pointer(&libc_asyscall)), "nb_args", ... )
|
||||
return
|
||||
}
|
||||
|
||||
zsyscall_aix_ppc64_ggcgo.go
|
||||
|
||||
// int asyscall(...)
|
||||
|
||||
import "C"
|
||||
|
||||
func callasyscall(...) (r1 uintptr, e1 Errno) {
|
||||
r1 = uintptr(C.asyscall(...))
|
||||
e1 = syscall.GetErrno()
|
||||
return
|
||||
}
|
||||
*/
|
||||
|
||||
package main
|
||||
|
||||
import (
|
||||
"bufio"
|
||||
"flag"
|
||||
"fmt"
|
||||
"io/ioutil"
|
||||
"os"
|
||||
"regexp"
|
||||
"strings"
|
||||
)
|
||||
|
||||
var (
|
||||
b32 = flag.Bool("b32", false, "32bit big-endian")
|
||||
l32 = flag.Bool("l32", false, "32bit little-endian")
|
||||
aix = flag.Bool("aix", false, "aix")
|
||||
tags = flag.String("tags", "", "build tags")
|
||||
)
|
||||
|
||||
// cmdLine returns this programs's commandline arguments
|
||||
func cmdLine() string {
|
||||
return "go run mksyscall_aix_ppc64.go " + strings.Join(os.Args[1:], " ")
|
||||
}
|
||||
|
||||
// buildTags returns build tags
|
||||
func buildTags() string {
|
||||
return *tags
|
||||
}
|
||||
|
||||
// Param is function parameter
|
||||
type Param struct {
|
||||
Name string
|
||||
Type string
|
||||
}
|
||||
|
||||
// usage prints the program usage
|
||||
func usage() {
|
||||
fmt.Fprintf(os.Stderr, "usage: go run mksyscall_aix_ppc64.go [-b32 | -l32] [-tags x,y] [file ...]\n")
|
||||
os.Exit(1)
|
||||
}
|
||||
|
||||
// parseParamList parses parameter list and returns a slice of parameters
|
||||
func parseParamList(list string) []string {
|
||||
list = strings.TrimSpace(list)
|
||||
if list == "" {
|
||||
return []string{}
|
||||
}
|
||||
return regexp.MustCompile(`\s*,\s*`).Split(list, -1)
|
||||
}
|
||||
|
||||
// parseParam splits a parameter into name and type
|
||||
func parseParam(p string) Param {
|
||||
ps := regexp.MustCompile(`^(\S*) (\S*)$`).FindStringSubmatch(p)
|
||||
if ps == nil {
|
||||
fmt.Fprintf(os.Stderr, "malformed parameter: %s\n", p)
|
||||
os.Exit(1)
|
||||
}
|
||||
return Param{ps[1], ps[2]}
|
||||
}
|
||||
|
||||
func main() {
|
||||
flag.Usage = usage
|
||||
flag.Parse()
|
||||
if len(flag.Args()) <= 0 {
|
||||
fmt.Fprintf(os.Stderr, "no files to parse provided\n")
|
||||
usage()
|
||||
}
|
||||
|
||||
endianness := ""
|
||||
if *b32 {
|
||||
endianness = "big-endian"
|
||||
} else if *l32 {
|
||||
endianness = "little-endian"
|
||||
}
|
||||
|
||||
pack := ""
|
||||
// GCCGO
|
||||
textgccgo := ""
|
||||
cExtern := "/*\n#include <stdint.h>\n"
|
||||
// GC
|
||||
textgc := ""
|
||||
dynimports := ""
|
||||
linknames := ""
|
||||
var vars []string
|
||||
// COMMON
|
||||
textcommon := ""
|
||||
for _, path := range flag.Args() {
|
||||
file, err := os.Open(path)
|
||||
if err != nil {
|
||||
fmt.Fprintf(os.Stderr, err.Error())
|
||||
os.Exit(1)
|
||||
}
|
||||
s := bufio.NewScanner(file)
|
||||
for s.Scan() {
|
||||
t := s.Text()
|
||||
t = strings.TrimSpace(t)
|
||||
t = regexp.MustCompile(`\s+`).ReplaceAllString(t, ` `)
|
||||
if p := regexp.MustCompile(`^package (\S+)$`).FindStringSubmatch(t); p != nil && pack == "" {
|
||||
pack = p[1]
|
||||
}
|
||||
nonblock := regexp.MustCompile(`^\/\/sysnb `).FindStringSubmatch(t)
|
||||
if regexp.MustCompile(`^\/\/sys `).FindStringSubmatch(t) == nil && nonblock == nil {
|
||||
continue
|
||||
}
|
||||
|
||||
// Line must be of the form
|
||||
// func Open(path string, mode int, perm int) (fd int, err error)
|
||||
// Split into name, in params, out params.
|
||||
f := regexp.MustCompile(`^\/\/sys(nb)? (\w+)\(([^()]*)\)\s*(?:\(([^()]+)\))?\s*(?:=\s*(?:(\w*)\.)?(\w*))?$`).FindStringSubmatch(t)
|
||||
if f == nil {
|
||||
fmt.Fprintf(os.Stderr, "%s:%s\nmalformed //sys declaration\n", path, t)
|
||||
os.Exit(1)
|
||||
}
|
||||
funct, inps, outps, modname, sysname := f[2], f[3], f[4], f[5], f[6]
|
||||
|
||||
// Split argument lists on comma.
|
||||
in := parseParamList(inps)
|
||||
out := parseParamList(outps)
|
||||
|
||||
inps = strings.Join(in, ", ")
|
||||
outps = strings.Join(out, ", ")
|
||||
|
||||
if sysname == "" {
|
||||
sysname = funct
|
||||
}
|
||||
|
||||
onlyCommon := false
|
||||
if funct == "readlen" || funct == "writelen" || funct == "FcntlInt" || funct == "FcntlFlock" {
|
||||
// This function call another syscall which is already implemented.
|
||||
// Therefore, the gc and gccgo part must not be generated.
|
||||
onlyCommon = true
|
||||
}
|
||||
|
||||
// Try in vain to keep people from editing this file.
|
||||
// The theory is that they jump into the middle of the file
|
||||
// without reading the header.
|
||||
|
||||
textcommon += "// THIS FILE IS GENERATED BY THE COMMAND AT THE TOP; DO NOT EDIT\n\n"
|
||||
if !onlyCommon {
|
||||
textgccgo += "// THIS FILE IS GENERATED BY THE COMMAND AT THE TOP; DO NOT EDIT\n\n"
|
||||
textgc += "// THIS FILE IS GENERATED BY THE COMMAND AT THE TOP; DO NOT EDIT\n\n"
|
||||
}
|
||||
|
||||
// Check if value return, err return available
|
||||
errvar := ""
|
||||
rettype := ""
|
||||
for _, param := range out {
|
||||
p := parseParam(param)
|
||||
if p.Type == "error" {
|
||||
errvar = p.Name
|
||||
} else {
|
||||
rettype = p.Type
|
||||
}
|
||||
}
|
||||
|
||||
sysname = regexp.MustCompile(`([a-z])([A-Z])`).ReplaceAllString(sysname, `${1}_$2`)
|
||||
sysname = strings.ToLower(sysname) // All libc functions are lowercase.
|
||||
|
||||
// GCCGO Prototype return type
|
||||
cRettype := ""
|
||||
if rettype == "unsafe.Pointer" {
|
||||
cRettype = "uintptr_t"
|
||||
} else if rettype == "uintptr" {
|
||||
cRettype = "uintptr_t"
|
||||
} else if regexp.MustCompile(`^_`).FindStringSubmatch(rettype) != nil {
|
||||
cRettype = "uintptr_t"
|
||||
} else if rettype == "int" {
|
||||
cRettype = "int"
|
||||
} else if rettype == "int32" {
|
||||
cRettype = "int"
|
||||
} else if rettype == "int64" {
|
||||
cRettype = "long long"
|
||||
} else if rettype == "uint32" {
|
||||
cRettype = "unsigned int"
|
||||
} else if rettype == "uint64" {
|
||||
cRettype = "unsigned long long"
|
||||
} else {
|
||||
cRettype = "int"
|
||||
}
|
||||
if sysname == "exit" {
|
||||
cRettype = "void"
|
||||
}
|
||||
|
||||
// GCCGO Prototype arguments type
|
||||
var cIn []string
|
||||
for i, param := range in {
|
||||
p := parseParam(param)
|
||||
if regexp.MustCompile(`^\*`).FindStringSubmatch(p.Type) != nil {
|
||||
cIn = append(cIn, "uintptr_t")
|
||||
} else if p.Type == "string" {
|
||||
cIn = append(cIn, "uintptr_t")
|
||||
} else if regexp.MustCompile(`^\[\](.*)`).FindStringSubmatch(p.Type) != nil {
|
||||
cIn = append(cIn, "uintptr_t", "size_t")
|
||||
} else if p.Type == "unsafe.Pointer" {
|
||||
cIn = append(cIn, "uintptr_t")
|
||||
} else if p.Type == "uintptr" {
|
||||
cIn = append(cIn, "uintptr_t")
|
||||
} else if regexp.MustCompile(`^_`).FindStringSubmatch(p.Type) != nil {
|
||||
cIn = append(cIn, "uintptr_t")
|
||||
} else if p.Type == "int" {
|
||||
if (i == 0 || i == 2) && funct == "fcntl" {
|
||||
// These fcntl arguments needs to be uintptr to be able to call FcntlInt and FcntlFlock
|
||||
cIn = append(cIn, "uintptr_t")
|
||||
} else {
|
||||
cIn = append(cIn, "int")
|
||||
}
|
||||
|
||||
} else if p.Type == "int32" {
|
||||
cIn = append(cIn, "int")
|
||||
} else if p.Type == "int64" {
|
||||
cIn = append(cIn, "long long")
|
||||
} else if p.Type == "uint32" {
|
||||
cIn = append(cIn, "unsigned int")
|
||||
} else if p.Type == "uint64" {
|
||||
cIn = append(cIn, "unsigned long long")
|
||||
} else {
|
||||
cIn = append(cIn, "int")
|
||||
}
|
||||
}
|
||||
|
||||
if !onlyCommon {
|
||||
// GCCGO Prototype Generation
|
||||
// Imports of system calls from libc
|
||||
cExtern += fmt.Sprintf("%s %s", cRettype, sysname)
|
||||
cIn := strings.Join(cIn, ", ")
|
||||
cExtern += fmt.Sprintf("(%s);\n", cIn)
|
||||
}
|
||||
// GC Library name
|
||||
if modname == "" {
|
||||
modname = "libc.a/shr_64.o"
|
||||
} else {
|
||||
fmt.Fprintf(os.Stderr, "%s: only syscall using libc are available\n", funct)
|
||||
os.Exit(1)
|
||||
}
|
||||
sysvarname := fmt.Sprintf("libc_%s", sysname)
|
||||
|
||||
if !onlyCommon {
|
||||
// GC Runtime import of function to allow cross-platform builds.
|
||||
dynimports += fmt.Sprintf("//go:cgo_import_dynamic %s %s \"%s\"\n", sysvarname, sysname, modname)
|
||||
// GC Link symbol to proc address variable.
|
||||
linknames += fmt.Sprintf("//go:linkname %s %s\n", sysvarname, sysvarname)
|
||||
// GC Library proc address variable.
|
||||
vars = append(vars, sysvarname)
|
||||
}
|
||||
|
||||
strconvfunc := "BytePtrFromString"
|
||||
strconvtype := "*byte"
|
||||
|
||||
// Go function header.
|
||||
if outps != "" {
|
||||
outps = fmt.Sprintf(" (%s)", outps)
|
||||
}
|
||||
if textcommon != "" {
|
||||
textcommon += "\n"
|
||||
}
|
||||
|
||||
textcommon += fmt.Sprintf("func %s(%s)%s {\n", funct, strings.Join(in, ", "), outps)
|
||||
|
||||
// Prepare arguments tocall.
|
||||
var argscommon []string // Arguments in the common part
|
||||
var argscall []string // Arguments for call prototype
|
||||
var argsgc []string // Arguments for gc call (with syscall6)
|
||||
var argsgccgo []string // Arguments for gccgo call (with C.name_of_syscall)
|
||||
n := 0
|
||||
argN := 0
|
||||
for _, param := range in {
|
||||
p := parseParam(param)
|
||||
if regexp.MustCompile(`^\*`).FindStringSubmatch(p.Type) != nil {
|
||||
argscommon = append(argscommon, fmt.Sprintf("uintptr(unsafe.Pointer(%s))", p.Name))
|
||||
argscall = append(argscall, fmt.Sprintf("%s uintptr", p.Name))
|
||||
argsgc = append(argsgc, p.Name)
|
||||
argsgccgo = append(argsgccgo, fmt.Sprintf("C.uintptr_t(%s)", p.Name))
|
||||
} else if p.Type == "string" && errvar != "" {
|
||||
textcommon += fmt.Sprintf("\tvar _p%d %s\n", n, strconvtype)
|
||||
textcommon += fmt.Sprintf("\t_p%d, %s = %s(%s)\n", n, errvar, strconvfunc, p.Name)
|
||||
textcommon += fmt.Sprintf("\tif %s != nil {\n\t\treturn\n\t}\n", errvar)
|
||||
|
||||
argscommon = append(argscommon, fmt.Sprintf("uintptr(unsafe.Pointer(_p%d))", n))
|
||||
argscall = append(argscall, fmt.Sprintf("_p%d uintptr ", n))
|
||||
argsgc = append(argsgc, fmt.Sprintf("_p%d", n))
|
||||
argsgccgo = append(argsgccgo, fmt.Sprintf("C.uintptr_t(_p%d)", n))
|
||||
n++
|
||||
} else if p.Type == "string" {
|
||||
fmt.Fprintf(os.Stderr, path+":"+funct+" uses string arguments, but has no error return\n")
|
||||
textcommon += fmt.Sprintf("\tvar _p%d %s\n", n, strconvtype)
|
||||
textcommon += fmt.Sprintf("\t_p%d, %s = %s(%s)\n", n, errvar, strconvfunc, p.Name)
|
||||
textcommon += fmt.Sprintf("\tif %s != nil {\n\t\treturn\n\t}\n", errvar)
|
||||
|
||||
argscommon = append(argscommon, fmt.Sprintf("uintptr(unsafe.Pointer(_p%d))", n))
|
||||
argscall = append(argscall, fmt.Sprintf("_p%d uintptr", n))
|
||||
argsgc = append(argsgc, fmt.Sprintf("_p%d", n))
|
||||
argsgccgo = append(argsgccgo, fmt.Sprintf("C.uintptr_t(_p%d)", n))
|
||||
n++
|
||||
} else if m := regexp.MustCompile(`^\[\](.*)`).FindStringSubmatch(p.Type); m != nil {
|
||||
// Convert slice into pointer, length.
|
||||
// Have to be careful not to take address of &a[0] if len == 0:
|
||||
// pass nil in that case.
|
||||
textcommon += fmt.Sprintf("\tvar _p%d *%s\n", n, m[1])
|
||||
textcommon += fmt.Sprintf("\tif len(%s) > 0 {\n\t\t_p%d = &%s[0]\n\t}\n", p.Name, n, p.Name)
|
||||
argscommon = append(argscommon, fmt.Sprintf("uintptr(unsafe.Pointer(_p%d))", n), fmt.Sprintf("len(%s)", p.Name))
|
||||
argscall = append(argscall, fmt.Sprintf("_p%d uintptr", n), fmt.Sprintf("_lenp%d int", n))
|
||||
argsgc = append(argsgc, fmt.Sprintf("_p%d", n), fmt.Sprintf("uintptr(_lenp%d)", n))
|
||||
argsgccgo = append(argsgccgo, fmt.Sprintf("C.uintptr_t(_p%d)", n), fmt.Sprintf("C.size_t(_lenp%d)", n))
|
||||
n++
|
||||
} else if p.Type == "int64" && endianness != "" {
|
||||
fmt.Fprintf(os.Stderr, path+":"+funct+" uses int64 with 32 bits mode. Case not yet implemented\n")
|
||||
} else if p.Type == "bool" {
|
||||
fmt.Fprintf(os.Stderr, path+":"+funct+" uses bool. Case not yet implemented\n")
|
||||
} else if regexp.MustCompile(`^_`).FindStringSubmatch(p.Type) != nil || p.Type == "unsafe.Pointer" {
|
||||
argscommon = append(argscommon, fmt.Sprintf("uintptr(%s)", p.Name))
|
||||
argscall = append(argscall, fmt.Sprintf("%s uintptr", p.Name))
|
||||
argsgc = append(argsgc, p.Name)
|
||||
argsgccgo = append(argsgccgo, fmt.Sprintf("C.uintptr_t(%s)", p.Name))
|
||||
} else if p.Type == "int" {
|
||||
if (argN == 0 || argN == 2) && ((funct == "fcntl") || (funct == "FcntlInt") || (funct == "FcntlFlock")) {
|
||||
// These fcntl arguments need to be uintptr to be able to call FcntlInt and FcntlFlock
|
||||
argscommon = append(argscommon, fmt.Sprintf("uintptr(%s)", p.Name))
|
||||
argscall = append(argscall, fmt.Sprintf("%s uintptr", p.Name))
|
||||
argsgc = append(argsgc, p.Name)
|
||||
argsgccgo = append(argsgccgo, fmt.Sprintf("C.uintptr_t(%s)", p.Name))
|
||||
|
||||
} else {
|
||||
argscommon = append(argscommon, p.Name)
|
||||
argscall = append(argscall, fmt.Sprintf("%s int", p.Name))
|
||||
argsgc = append(argsgc, fmt.Sprintf("uintptr(%s)", p.Name))
|
||||
argsgccgo = append(argsgccgo, fmt.Sprintf("C.int(%s)", p.Name))
|
||||
}
|
||||
} else if p.Type == "int32" {
|
||||
argscommon = append(argscommon, p.Name)
|
||||
argscall = append(argscall, fmt.Sprintf("%s int32", p.Name))
|
||||
argsgc = append(argsgc, fmt.Sprintf("uintptr(%s)", p.Name))
|
||||
argsgccgo = append(argsgccgo, fmt.Sprintf("C.int(%s)", p.Name))
|
||||
} else if p.Type == "int64" {
|
||||
argscommon = append(argscommon, p.Name)
|
||||
argscall = append(argscall, fmt.Sprintf("%s int64", p.Name))
|
||||
argsgc = append(argsgc, fmt.Sprintf("uintptr(%s)", p.Name))
|
||||
argsgccgo = append(argsgccgo, fmt.Sprintf("C.longlong(%s)", p.Name))
|
||||
} else if p.Type == "uint32" {
|
||||
argscommon = append(argscommon, p.Name)
|
||||
argscall = append(argscall, fmt.Sprintf("%s uint32", p.Name))
|
||||
argsgc = append(argsgc, fmt.Sprintf("uintptr(%s)", p.Name))
|
||||
argsgccgo = append(argsgccgo, fmt.Sprintf("C.uint(%s)", p.Name))
|
||||
} else if p.Type == "uint64" {
|
||||
argscommon = append(argscommon, p.Name)
|
||||
argscall = append(argscall, fmt.Sprintf("%s uint64", p.Name))
|
||||
argsgc = append(argsgc, fmt.Sprintf("uintptr(%s)", p.Name))
|
||||
argsgccgo = append(argsgccgo, fmt.Sprintf("C.ulonglong(%s)", p.Name))
|
||||
} else if p.Type == "uintptr" {
|
||||
argscommon = append(argscommon, p.Name)
|
||||
argscall = append(argscall, fmt.Sprintf("%s uintptr", p.Name))
|
||||
argsgc = append(argsgc, p.Name)
|
||||
argsgccgo = append(argsgccgo, fmt.Sprintf("C.uintptr_t(%s)", p.Name))
|
||||
} else {
|
||||
argscommon = append(argscommon, fmt.Sprintf("int(%s)", p.Name))
|
||||
argscall = append(argscall, fmt.Sprintf("%s int", p.Name))
|
||||
argsgc = append(argsgc, fmt.Sprintf("uintptr(%s)", p.Name))
|
||||
argsgccgo = append(argsgccgo, fmt.Sprintf("C.int(%s)", p.Name))
|
||||
}
|
||||
argN++
|
||||
}
|
||||
nargs := len(argsgc)
|
||||
|
||||
// COMMON function generation
|
||||
argscommonlist := strings.Join(argscommon, ", ")
|
||||
callcommon := fmt.Sprintf("call%s(%s)", sysname, argscommonlist)
|
||||
ret := []string{"_", "_"}
|
||||
body := ""
|
||||
doErrno := false
|
||||
for i := 0; i < len(out); i++ {
|
||||
p := parseParam(out[i])
|
||||
reg := ""
|
||||
if p.Name == "err" {
|
||||
reg = "e1"
|
||||
ret[1] = reg
|
||||
doErrno = true
|
||||
} else {
|
||||
reg = "r0"
|
||||
ret[0] = reg
|
||||
}
|
||||
if p.Type == "bool" {
|
||||
reg = fmt.Sprintf("%s != 0", reg)
|
||||
}
|
||||
if reg != "e1" {
|
||||
body += fmt.Sprintf("\t%s = %s(%s)\n", p.Name, p.Type, reg)
|
||||
}
|
||||
}
|
||||
if ret[0] == "_" && ret[1] == "_" {
|
||||
textcommon += fmt.Sprintf("\t%s\n", callcommon)
|
||||
} else {
|
||||
textcommon += fmt.Sprintf("\t%s, %s := %s\n", ret[0], ret[1], callcommon)
|
||||
}
|
||||
textcommon += body
|
||||
|
||||
if doErrno {
|
||||
textcommon += "\tif e1 != 0 {\n"
|
||||
textcommon += "\t\terr = errnoErr(e1)\n"
|
||||
textcommon += "\t}\n"
|
||||
}
|
||||
textcommon += "\treturn\n"
|
||||
textcommon += "}\n"
|
||||
|
||||
if onlyCommon {
|
||||
continue
|
||||
}
|
||||
|
||||
// CALL Prototype
|
||||
callProto := fmt.Sprintf("func call%s(%s) (r1 uintptr, e1 Errno) {\n", sysname, strings.Join(argscall, ", "))
|
||||
|
||||
// GC function generation
|
||||
asm := "syscall6"
|
||||
if nonblock != nil {
|
||||
asm = "rawSyscall6"
|
||||
}
|
||||
|
||||
if len(argsgc) <= 6 {
|
||||
for len(argsgc) < 6 {
|
||||
argsgc = append(argsgc, "0")
|
||||
}
|
||||
} else {
|
||||
fmt.Fprintf(os.Stderr, "%s: too many arguments to system call", funct)
|
||||
os.Exit(1)
|
||||
}
|
||||
argsgclist := strings.Join(argsgc, ", ")
|
||||
callgc := fmt.Sprintf("%s(uintptr(unsafe.Pointer(&%s)), %d, %s)", asm, sysvarname, nargs, argsgclist)
|
||||
|
||||
textgc += callProto
|
||||
textgc += fmt.Sprintf("\tr1, _, e1 = %s\n", callgc)
|
||||
textgc += "\treturn\n}\n"
|
||||
|
||||
// GCCGO function generation
|
||||
argsgccgolist := strings.Join(argsgccgo, ", ")
|
||||
callgccgo := fmt.Sprintf("C.%s(%s)", sysname, argsgccgolist)
|
||||
textgccgo += callProto
|
||||
textgccgo += fmt.Sprintf("\tr1 = uintptr(%s)\n", callgccgo)
|
||||
textgccgo += "\te1 = syscall.GetErrno()\n"
|
||||
textgccgo += "\treturn\n}\n"
|
||||
}
|
||||
if err := s.Err(); err != nil {
|
||||
fmt.Fprintf(os.Stderr, err.Error())
|
||||
os.Exit(1)
|
||||
}
|
||||
file.Close()
|
||||
}
|
||||
imp := ""
|
||||
if pack != "unix" {
|
||||
imp = "import \"golang.org/x/sys/unix\"\n"
|
||||
|
||||
}
|
||||
|
||||
// Print zsyscall_aix_ppc64.go
|
||||
err := ioutil.WriteFile("zsyscall_aix_ppc64.go",
|
||||
[]byte(fmt.Sprintf(srcTemplate1, cmdLine(), buildTags(), pack, imp, textcommon)),
|
||||
0644)
|
||||
if err != nil {
|
||||
fmt.Fprintf(os.Stderr, err.Error())
|
||||
os.Exit(1)
|
||||
}
|
||||
|
||||
// Print zsyscall_aix_ppc64_gc.go
|
||||
vardecls := "\t" + strings.Join(vars, ",\n\t")
|
||||
vardecls += " syscallFunc"
|
||||
err = ioutil.WriteFile("zsyscall_aix_ppc64_gc.go",
|
||||
[]byte(fmt.Sprintf(srcTemplate2, cmdLine(), buildTags(), pack, imp, dynimports, linknames, vardecls, textgc)),
|
||||
0644)
|
||||
if err != nil {
|
||||
fmt.Fprintf(os.Stderr, err.Error())
|
||||
os.Exit(1)
|
||||
}
|
||||
|
||||
// Print zsyscall_aix_ppc64_gccgo.go
|
||||
err = ioutil.WriteFile("zsyscall_aix_ppc64_gccgo.go",
|
||||
[]byte(fmt.Sprintf(srcTemplate3, cmdLine(), buildTags(), pack, cExtern, imp, textgccgo)),
|
||||
0644)
|
||||
if err != nil {
|
||||
fmt.Fprintf(os.Stderr, err.Error())
|
||||
os.Exit(1)
|
||||
}
|
||||
}
|
||||
|
||||
const srcTemplate1 = `// %s
|
||||
// Code generated by the command above; see README.md. DO NOT EDIT.
|
||||
|
||||
// +build %s
|
||||
|
||||
package %s
|
||||
|
||||
import (
|
||||
"unsafe"
|
||||
)
|
||||
|
||||
|
||||
%s
|
||||
|
||||
%s
|
||||
`
|
||||
const srcTemplate2 = `// %s
|
||||
// Code generated by the command above; see README.md. DO NOT EDIT.
|
||||
|
||||
// +build %s
|
||||
// +build !gccgo
|
||||
|
||||
package %s
|
||||
|
||||
import (
|
||||
"unsafe"
|
||||
)
|
||||
%s
|
||||
%s
|
||||
%s
|
||||
type syscallFunc uintptr
|
||||
|
||||
var (
|
||||
%s
|
||||
)
|
||||
|
||||
// Implemented in runtime/syscall_aix.go.
|
||||
func rawSyscall6(trap, nargs, a1, a2, a3, a4, a5, a6 uintptr) (r1, r2 uintptr, err Errno)
|
||||
func syscall6(trap, nargs, a1, a2, a3, a4, a5, a6 uintptr) (r1, r2 uintptr, err Errno)
|
||||
|
||||
%s
|
||||
`
|
||||
const srcTemplate3 = `// %s
|
||||
// Code generated by the command above; see README.md. DO NOT EDIT.
|
||||
|
||||
// +build %s
|
||||
// +build gccgo
|
||||
|
||||
package %s
|
||||
|
||||
%s
|
||||
*/
|
||||
import "C"
|
||||
import (
|
||||
"syscall"
|
||||
)
|
||||
|
||||
|
||||
%s
|
||||
|
||||
%s
|
||||
`
|
579
vendor/golang.org/x/sys/unix/mksyscall_aix_ppc64.pl
generated
vendored
579
vendor/golang.org/x/sys/unix/mksyscall_aix_ppc64.pl
generated
vendored
|
@ -1,579 +0,0 @@
|
|||
#!/usr/bin/env perl
|
||||
# Copyright 2018 The Go Authors. All rights reserved.
|
||||
# Use of this source code is governed by a BSD-style
|
||||
# license that can be found in the LICENSE file.
|
||||
|
||||
# This program reads a file containing function prototypes
|
||||
# (like syscall_aix.go) and generates system call bodies.
|
||||
# The prototypes are marked by lines beginning with "//sys"
|
||||
# and read like func declarations if //sys is replaced by func, but:
|
||||
# * The parameter lists must give a name for each argument.
|
||||
# This includes return parameters.
|
||||
# * The parameter lists must give a type for each argument:
|
||||
# the (x, y, z int) shorthand is not allowed.
|
||||
# * If the return parameter is an error number, it must be named err.
|
||||
# * If go func name needs to be different than its libc name,
|
||||
# * or the function is not in libc, name could be specified
|
||||
# * at the end, after "=" sign, like
|
||||
# //sys getsockopt(s int, level int, name int, val uintptr, vallen *_Socklen) (err error) = libsocket.getsockopt
|
||||
|
||||
# This program will generate three files and handle both gc and gccgo implementation:
|
||||
# - zsyscall_aix_ppc64.go: the common part of each implementation (error handler, pointer creation)
|
||||
# - zsyscall_aix_ppc64_gc.go: gc part with //go_cgo_import_dynamic and a call to syscall6
|
||||
# - zsyscall_aix_ppc64_gccgo.go: gccgo part with C function and conversion to C type.
|
||||
|
||||
# The generated code looks like this
|
||||
#
|
||||
# zsyscall_aix_ppc64.go
|
||||
# func asyscall(...) (n int, err error) {
|
||||
# // Pointer Creation
|
||||
# r1, e1 := callasyscall(...)
|
||||
# // Type Conversion
|
||||
# // Error Handler
|
||||
# return
|
||||
# }
|
||||
#
|
||||
# zsyscall_aix_ppc64_gc.go
|
||||
# //go:cgo_import_dynamic libc_asyscall asyscall "libc.a/shr_64.o"
|
||||
# //go:linkname libc_asyscall libc_asyscall
|
||||
# var asyscall syscallFunc
|
||||
#
|
||||
# func callasyscall(...) (r1 uintptr, e1 Errno) {
|
||||
# r1, _, e1 = syscall6(uintptr(unsafe.Pointer(&libc_asyscall)), "nb_args", ... )
|
||||
# return
|
||||
# }
|
||||
#
|
||||
# zsyscall_aix_ppc64_ggcgo.go
|
||||
# /*
|
||||
# int asyscall(...)
|
||||
#
|
||||
# */
|
||||
# import "C"
|
||||
#
|
||||
# func callasyscall(...) (r1 uintptr, e1 Errno) {
|
||||
# r1 = uintptr(C.asyscall(...))
|
||||
# e1 = syscall.GetErrno()
|
||||
# return
|
||||
# }
|
||||
|
||||
|
||||
|
||||
use strict;
|
||||
|
||||
my $cmdline = "mksyscall_aix_ppc64.pl " . join(' ', @ARGV);
|
||||
my $errors = 0;
|
||||
my $_32bit = "";
|
||||
my $tags = ""; # build tags
|
||||
my $aix = 0;
|
||||
my $solaris = 0;
|
||||
|
||||
binmode STDOUT;
|
||||
|
||||
if($ARGV[0] eq "-b32") {
|
||||
$_32bit = "big-endian";
|
||||
shift;
|
||||
} elsif($ARGV[0] eq "-l32") {
|
||||
$_32bit = "little-endian";
|
||||
shift;
|
||||
}
|
||||
if($ARGV[0] eq "-aix") {
|
||||
$aix = 1;
|
||||
shift;
|
||||
}
|
||||
if($ARGV[0] eq "-tags") {
|
||||
shift;
|
||||
$tags = $ARGV[0];
|
||||
shift;
|
||||
}
|
||||
|
||||
if($ARGV[0] =~ /^-/) {
|
||||
print STDERR "usage: mksyscall_aix.pl [-b32 | -l32] [-tags x,y] [file ...]\n";
|
||||
exit 1;
|
||||
}
|
||||
|
||||
sub parseparamlist($) {
|
||||
my ($list) = @_;
|
||||
$list =~ s/^\s*//;
|
||||
$list =~ s/\s*$//;
|
||||
if($list eq "") {
|
||||
return ();
|
||||
}
|
||||
return split(/\s*,\s*/, $list);
|
||||
}
|
||||
|
||||
sub parseparam($) {
|
||||
my ($p) = @_;
|
||||
if($p !~ /^(\S*) (\S*)$/) {
|
||||
print STDERR "$ARGV:$.: malformed parameter: $p\n";
|
||||
$errors = 1;
|
||||
return ("xx", "int");
|
||||
}
|
||||
return ($1, $2);
|
||||
}
|
||||
|
||||
my $package = "";
|
||||
# GCCGO
|
||||
my $textgccgo = "";
|
||||
my $c_extern = "/*\n#include <stdint.h>\n";
|
||||
# GC
|
||||
my $textgc = "";
|
||||
my $dynimports = "";
|
||||
my $linknames = "";
|
||||
my @vars = ();
|
||||
# COMMUN
|
||||
my $textcommon = "";
|
||||
|
||||
while(<>) {
|
||||
chomp;
|
||||
s/\s+/ /g;
|
||||
s/^\s+//;
|
||||
s/\s+$//;
|
||||
$package = $1 if !$package && /^package (\S+)$/;
|
||||
my $nonblock = /^\/\/sysnb /;
|
||||
next if !/^\/\/sys / && !$nonblock;
|
||||
|
||||
# Line must be of the form
|
||||
# func Open(path string, mode int, perm int) (fd int, err error)
|
||||
# Split into name, in params, out params.
|
||||
if(!/^\/\/sys(nb)? (\w+)\(([^()]*)\)\s*(?:\(([^()]+)\))?\s*(?:=\s*(?:(\w*)\.)?(\w*))?$/) {
|
||||
print STDERR "$ARGV:$.: malformed //sys declaration\n";
|
||||
$errors = 1;
|
||||
next;
|
||||
}
|
||||
my ($nb, $func, $in, $out, $modname, $sysname) = ($1, $2, $3, $4, $5, $6);
|
||||
|
||||
# Split argument lists on comma.
|
||||
my @in = parseparamlist($in);
|
||||
my @out = parseparamlist($out);
|
||||
|
||||
$in = join(', ', @in);
|
||||
$out = join(', ', @out);
|
||||
|
||||
if($sysname eq "") {
|
||||
$sysname = "$func";
|
||||
}
|
||||
|
||||
my $onlyCommon = 0;
|
||||
if ($func eq "readlen" || $func eq "writelen" || $func eq "FcntlInt" || $func eq "FcntlFlock") {
|
||||
# This function call another syscall which is already implemented.
|
||||
# Therefore, the gc and gccgo part must not be generated.
|
||||
$onlyCommon = 1
|
||||
}
|
||||
|
||||
# Try in vain to keep people from editing this file.
|
||||
# The theory is that they jump into the middle of the file
|
||||
# without reading the header.
|
||||
|
||||
$textcommon .= "// THIS FILE IS GENERATED BY THE COMMAND AT THE TOP; DO NOT EDIT\n\n";
|
||||
if (!$onlyCommon) {
|
||||
$textgccgo .= "// THIS FILE IS GENERATED BY THE COMMAND AT THE TOP; DO NOT EDIT\n\n";
|
||||
$textgc .= "// THIS FILE IS GENERATED BY THE COMMAND AT THE TOP; DO NOT EDIT\n\n";
|
||||
}
|
||||
|
||||
|
||||
# Check if value return, err return available
|
||||
my $errvar = "";
|
||||
my $retvar = "";
|
||||
my $rettype = "";
|
||||
foreach my $p (@out) {
|
||||
my ($name, $type) = parseparam($p);
|
||||
if($type eq "error") {
|
||||
$errvar = $name;
|
||||
} else {
|
||||
$retvar = $name;
|
||||
$rettype = $type;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
$sysname =~ s/([a-z])([A-Z])/${1}_$2/g;
|
||||
$sysname =~ y/A-Z/a-z/; # All libc functions are lowercase.
|
||||
|
||||
# GCCGO Prototype return type
|
||||
my $C_rettype = "";
|
||||
if($rettype eq "unsafe.Pointer") {
|
||||
$C_rettype = "uintptr_t";
|
||||
} elsif($rettype eq "uintptr") {
|
||||
$C_rettype = "uintptr_t";
|
||||
} elsif($rettype =~ /^_/) {
|
||||
$C_rettype = "uintptr_t";
|
||||
} elsif($rettype eq "int") {
|
||||
$C_rettype = "int";
|
||||
} elsif($rettype eq "int32") {
|
||||
$C_rettype = "int";
|
||||
} elsif($rettype eq "int64") {
|
||||
$C_rettype = "long long";
|
||||
} elsif($rettype eq "uint32") {
|
||||
$C_rettype = "unsigned int";
|
||||
} elsif($rettype eq "uint64") {
|
||||
$C_rettype = "unsigned long long";
|
||||
} else {
|
||||
$C_rettype = "int";
|
||||
}
|
||||
if($sysname eq "exit") {
|
||||
$C_rettype = "void";
|
||||
}
|
||||
|
||||
# GCCGO Prototype arguments type
|
||||
my @c_in = ();
|
||||
foreach my $i (0 .. $#in) {
|
||||
my ($name, $type) = parseparam($in[$i]);
|
||||
if($type =~ /^\*/) {
|
||||
push @c_in, "uintptr_t";
|
||||
} elsif($type eq "string") {
|
||||
push @c_in, "uintptr_t";
|
||||
} elsif($type =~ /^\[\](.*)/) {
|
||||
push @c_in, "uintptr_t", "size_t";
|
||||
} elsif($type eq "unsafe.Pointer") {
|
||||
push @c_in, "uintptr_t";
|
||||
} elsif($type eq "uintptr") {
|
||||
push @c_in, "uintptr_t";
|
||||
} elsif($type =~ /^_/) {
|
||||
push @c_in, "uintptr_t";
|
||||
} elsif($type eq "int") {
|
||||
if (($i == 0 || $i == 2) && $func eq "fcntl"){
|
||||
# These fcntl arguments needs to be uintptr to be able to call FcntlInt and FcntlFlock
|
||||
push @c_in, "uintptr_t";
|
||||
} else {
|
||||
push @c_in, "int";
|
||||
}
|
||||
} elsif($type eq "int32") {
|
||||
push @c_in, "int";
|
||||
} elsif($type eq "int64") {
|
||||
push @c_in, "long long";
|
||||
} elsif($type eq "uint32") {
|
||||
push @c_in, "unsigned int";
|
||||
} elsif($type eq "uint64") {
|
||||
push @c_in, "unsigned long long";
|
||||
} else {
|
||||
push @c_in, "int";
|
||||
}
|
||||
}
|
||||
|
||||
if (!$onlyCommon){
|
||||
# GCCGO Prototype Generation
|
||||
# Imports of system calls from libc
|
||||
$c_extern .= "$C_rettype $sysname";
|
||||
my $c_in = join(', ', @c_in);
|
||||
$c_extern .= "($c_in);\n";
|
||||
}
|
||||
|
||||
# GC Library name
|
||||
if($modname eq "") {
|
||||
$modname = "libc.a/shr_64.o";
|
||||
} else {
|
||||
print STDERR "$func: only syscall using libc are available\n";
|
||||
$errors = 1;
|
||||
next;
|
||||
}
|
||||
my $sysvarname = "libc_${sysname}";
|
||||
|
||||
if (!$onlyCommon){
|
||||
# GC Runtime import of function to allow cross-platform builds.
|
||||
$dynimports .= "//go:cgo_import_dynamic ${sysvarname} ${sysname} \"$modname\"\n";
|
||||
# GC Link symbol to proc address variable.
|
||||
$linknames .= "//go:linkname ${sysvarname} ${sysvarname}\n";
|
||||
# GC Library proc address variable.
|
||||
push @vars, $sysvarname;
|
||||
}
|
||||
|
||||
my $strconvfunc ="BytePtrFromString";
|
||||
my $strconvtype = "*byte";
|
||||
|
||||
# Go function header.
|
||||
if($out ne "") {
|
||||
$out = " ($out)";
|
||||
}
|
||||
if($textcommon ne "") {
|
||||
$textcommon .= "\n"
|
||||
}
|
||||
|
||||
$textcommon .= sprintf "func %s(%s)%s {\n", $func, join(', ', @in), $out ;
|
||||
|
||||
# Prepare arguments to call.
|
||||
my @argscommun = (); # Arguments in the commun part
|
||||
my @argscall = (); # Arguments for call prototype
|
||||
my @argsgc = (); # Arguments for gc call (with syscall6)
|
||||
my @argsgccgo = (); # Arguments for gccgo call (with C.name_of_syscall)
|
||||
my $n = 0;
|
||||
my $arg_n = 0;
|
||||
foreach my $p (@in) {
|
||||
my ($name, $type) = parseparam($p);
|
||||
if($type =~ /^\*/) {
|
||||
push @argscommun, "uintptr(unsafe.Pointer($name))";
|
||||
push @argscall, "$name uintptr";
|
||||
push @argsgc, "$name";
|
||||
push @argsgccgo, "C.uintptr_t($name)";
|
||||
} elsif($type eq "string" && $errvar ne "") {
|
||||
$textcommon .= "\tvar _p$n $strconvtype\n";
|
||||
$textcommon .= "\t_p$n, $errvar = $strconvfunc($name)\n";
|
||||
$textcommon .= "\tif $errvar != nil {\n\t\treturn\n\t}\n";
|
||||
|
||||
push @argscommun, "uintptr(unsafe.Pointer(_p$n))";
|
||||
push @argscall, "_p$n uintptr ";
|
||||
push @argsgc, "_p$n";
|
||||
push @argsgccgo, "C.uintptr_t(_p$n)";
|
||||
$n++;
|
||||
} elsif($type eq "string") {
|
||||
print STDERR "$ARGV:$.: $func uses string arguments, but has no error return\n";
|
||||
$textcommon .= "\tvar _p$n $strconvtype\n";
|
||||
$textcommon .= "\t_p$n, $errvar = $strconvfunc($name)\n";
|
||||
$textcommon .= "\tif $errvar != nil {\n\t\treturn\n\t}\n";
|
||||
|
||||
push @argscommun, "uintptr(unsafe.Pointer(_p$n))";
|
||||
push @argscall, "_p$n uintptr";
|
||||
push @argsgc, "_p$n";
|
||||
push @argsgccgo, "C.uintptr_t(_p$n)";
|
||||
$n++;
|
||||
} elsif($type =~ /^\[\](.*)/) {
|
||||
# Convert slice into pointer, length.
|
||||
# Have to be careful not to take address of &a[0] if len == 0:
|
||||
# pass nil in that case.
|
||||
$textcommon .= "\tvar _p$n *$1\n";
|
||||
$textcommon .= "\tif len($name) > 0 {\n\t\t_p$n = \&$name\[0]\n\t}\n";
|
||||
push @argscommun, "uintptr(unsafe.Pointer(_p$n))", "len($name)";
|
||||
push @argscall, "_p$n uintptr", "_lenp$n int";
|
||||
push @argsgc, "_p$n", "uintptr(_lenp$n)";
|
||||
push @argsgccgo, "C.uintptr_t(_p$n)", "C.size_t(_lenp$n)";
|
||||
$n++;
|
||||
} elsif($type eq "int64" && $_32bit ne "") {
|
||||
print STDERR "$ARGV:$.: $func uses int64 with 32 bits mode. Case not yet implemented\n";
|
||||
# if($_32bit eq "big-endian") {
|
||||
# push @args, "uintptr($name >> 32)", "uintptr($name)";
|
||||
# } else {
|
||||
# push @args, "uintptr($name)", "uintptr($name >> 32)";
|
||||
# }
|
||||
# $n++;
|
||||
} elsif($type eq "bool") {
|
||||
print STDERR "$ARGV:$.: $func uses bool. Case not yet implemented\n";
|
||||
# $text .= "\tvar _p$n uint32\n";
|
||||
# $text .= "\tif $name {\n\t\t_p$n = 1\n\t} else {\n\t\t_p$n = 0\n\t}\n";
|
||||
# push @args, "_p$n";
|
||||
# $n++;
|
||||
} elsif($type =~ /^_/ ||$type eq "unsafe.Pointer") {
|
||||
push @argscommun, "uintptr($name)";
|
||||
push @argscall, "$name uintptr";
|
||||
push @argsgc, "$name";
|
||||
push @argsgccgo, "C.uintptr_t($name)";
|
||||
} elsif($type eq "int") {
|
||||
if (($arg_n == 0 || $arg_n == 2) && ($func eq "fcntl" || $func eq "FcntlInt" || $func eq "FcntlFlock")) {
|
||||
# These fcntl arguments need to be uintptr to be able to call FcntlInt and FcntlFlock
|
||||
push @argscommun, "uintptr($name)";
|
||||
push @argscall, "$name uintptr";
|
||||
push @argsgc, "$name";
|
||||
push @argsgccgo, "C.uintptr_t($name)";
|
||||
} else {
|
||||
push @argscommun, "$name";
|
||||
push @argscall, "$name int";
|
||||
push @argsgc, "uintptr($name)";
|
||||
push @argsgccgo, "C.int($name)";
|
||||
}
|
||||
} elsif($type eq "int32") {
|
||||
push @argscommun, "$name";
|
||||
push @argscall, "$name int32";
|
||||
push @argsgc, "uintptr($name)";
|
||||
push @argsgccgo, "C.int($name)";
|
||||
} elsif($type eq "int64") {
|
||||
push @argscommun, "$name";
|
||||
push @argscall, "$name int64";
|
||||
push @argsgc, "uintptr($name)";
|
||||
push @argsgccgo, "C.longlong($name)";
|
||||
} elsif($type eq "uint32") {
|
||||
push @argscommun, "$name";
|
||||
push @argscall, "$name uint32";
|
||||
push @argsgc, "uintptr($name)";
|
||||
push @argsgccgo, "C.uint($name)";
|
||||
} elsif($type eq "uint64") {
|
||||
push @argscommun, "$name";
|
||||
push @argscall, "$name uint64";
|
||||
push @argsgc, "uintptr($name)";
|
||||
push @argsgccgo, "C.ulonglong($name)";
|
||||
} elsif($type eq "uintptr") {
|
||||
push @argscommun, "$name";
|
||||
push @argscall, "$name uintptr";
|
||||
push @argsgc, "$name";
|
||||
push @argsgccgo, "C.uintptr_t($name)";
|
||||
} else {
|
||||
push @argscommun, "int($name)";
|
||||
push @argscall, "$name int";
|
||||
push @argsgc, "uintptr($name)";
|
||||
push @argsgccgo, "C.int($name)";
|
||||
}
|
||||
$arg_n++;
|
||||
}
|
||||
my $nargs = @argsgc;
|
||||
|
||||
# COMMUN function generation
|
||||
my $argscommun = join(', ', @argscommun);
|
||||
my $callcommun = "call$sysname($argscommun)";
|
||||
my @ret = ("_", "_");
|
||||
my $body = "";
|
||||
my $do_errno = 0;
|
||||
for(my $i=0; $i<@out; $i++) {
|
||||
my $p = $out[$i];
|
||||
my ($name, $type) = parseparam($p);
|
||||
my $reg = "";
|
||||
if($name eq "err") {
|
||||
$reg = "e1";
|
||||
$ret[1] = $reg;
|
||||
$do_errno = 1;
|
||||
} else {
|
||||
$reg = "r0";
|
||||
$ret[0] = $reg;
|
||||
}
|
||||
if($type eq "bool") {
|
||||
$reg = "$reg != 0";
|
||||
}
|
||||
if($reg ne "e1") {
|
||||
$body .= "\t$name = $type($reg)\n";
|
||||
}
|
||||
}
|
||||
if ($ret[0] eq "_" && $ret[1] eq "_") {
|
||||
$textcommon .= "\t$callcommun\n";
|
||||
} else {
|
||||
$textcommon .= "\t$ret[0], $ret[1] := $callcommun\n";
|
||||
}
|
||||
$textcommon .= $body;
|
||||
|
||||
if ($do_errno) {
|
||||
$textcommon .= "\tif e1 != 0 {\n";
|
||||
$textcommon .= "\t\terr = errnoErr(e1)\n";
|
||||
$textcommon .= "\t}\n";
|
||||
}
|
||||
$textcommon .= "\treturn\n";
|
||||
$textcommon .= "}\n";
|
||||
|
||||
if ($onlyCommon){
|
||||
next
|
||||
}
|
||||
# CALL Prototype
|
||||
my $callProto = sprintf "func call%s(%s) (r1 uintptr, e1 Errno) {\n", $sysname, join(', ', @argscall);
|
||||
|
||||
# GC function generation
|
||||
my $asm = "syscall6";
|
||||
if ($nonblock) {
|
||||
$asm = "rawSyscall6";
|
||||
}
|
||||
|
||||
if(@argsgc <= 6) {
|
||||
while(@argsgc < 6) {
|
||||
push @argsgc, "0";
|
||||
}
|
||||
} else {
|
||||
print STDERR "$ARGV:$.: too many arguments to system call\n";
|
||||
}
|
||||
my $argsgc = join(', ', @argsgc);
|
||||
my $callgc = "$asm(uintptr(unsafe.Pointer(&$sysvarname)), $nargs, $argsgc)";
|
||||
|
||||
$textgc .= $callProto;
|
||||
$textgc .= "\tr1, _, e1 = $callgc\n";
|
||||
$textgc .= "\treturn\n}\n";
|
||||
|
||||
# GCCGO function generation
|
||||
my $argsgccgo = join(', ', @argsgccgo);
|
||||
my $callgccgo = "C.$sysname($argsgccgo)";
|
||||
$textgccgo .= $callProto;
|
||||
$textgccgo .= "\tr1 = uintptr($callgccgo)\n";
|
||||
$textgccgo .= "\te1 = syscall.GetErrno()\n";
|
||||
$textgccgo .= "\treturn\n}\n";
|
||||
}
|
||||
|
||||
if($errors) {
|
||||
exit 1;
|
||||
}
|
||||
|
||||
# Print zsyscall_aix_ppc64.go
|
||||
open(my $fcommun, '>', 'zsyscall_aix_ppc64.go');
|
||||
my $tofcommun = <<EOF;
|
||||
// $cmdline
|
||||
// Code generated by the command above; see README.md. DO NOT EDIT.
|
||||
|
||||
// +build $tags
|
||||
|
||||
package $package
|
||||
|
||||
import (
|
||||
"unsafe"
|
||||
)
|
||||
|
||||
EOF
|
||||
|
||||
$tofcommun .= "import \"golang.org/x/sys/unix\"\n" if $package ne "unix";
|
||||
|
||||
$tofcommun .=<<EOF;
|
||||
|
||||
$textcommon
|
||||
EOF
|
||||
print $fcommun $tofcommun;
|
||||
|
||||
|
||||
# Print zsyscall_aix_ppc64_gc.go
|
||||
open(my $fgc, '>', 'zsyscall_aix_ppc64_gc.go');
|
||||
my $tofgc = <<EOF;
|
||||
// $cmdline
|
||||
// Code generated by the command above; see README.md. DO NOT EDIT.
|
||||
|
||||
// +build $tags
|
||||
// +build !gccgo
|
||||
|
||||
package $package
|
||||
|
||||
import (
|
||||
"unsafe"
|
||||
)
|
||||
|
||||
|
||||
EOF
|
||||
|
||||
$tofgc .= "import \"golang.org/x/sys/unix\"\n" if $package ne "unix";
|
||||
|
||||
my $vardecls = "\t" . join(",\n\t", @vars);
|
||||
$vardecls .= " syscallFunc";
|
||||
|
||||
$tofgc .=<<EOF;
|
||||
$dynimports
|
||||
$linknames
|
||||
type syscallFunc uintptr
|
||||
|
||||
var (
|
||||
$vardecls
|
||||
)
|
||||
|
||||
// Implemented in runtime/syscall_aix.go.
|
||||
func rawSyscall6(trap, nargs, a1, a2, a3, a4, a5, a6 uintptr) (r1, r2 uintptr, err Errno)
|
||||
func syscall6(trap, nargs, a1, a2, a3, a4, a5, a6 uintptr) (r1, r2 uintptr, err Errno)
|
||||
|
||||
$textgc
|
||||
EOF
|
||||
print $fgc $tofgc;
|
||||
|
||||
# Print zsyscall_aix_ppc64_gc.go
|
||||
open(my $fgccgo, '>', 'zsyscall_aix_ppc64_gccgo.go');
|
||||
my $tofgccgo = <<EOF;
|
||||
// $cmdline
|
||||
// Code generated by the command above; see README.md. DO NOT EDIT.
|
||||
|
||||
// +build $tags
|
||||
// +build gccgo
|
||||
|
||||
package $package
|
||||
|
||||
|
||||
$c_extern
|
||||
*/
|
||||
import "C"
|
||||
import (
|
||||
"syscall"
|
||||
)
|
||||
|
||||
|
||||
EOF
|
||||
|
||||
$tofgccgo .= "import \"golang.org/x/sys/unix\"\n" if $package ne "unix";
|
||||
|
||||
$tofgccgo .=<<EOF;
|
||||
|
||||
$textgccgo
|
||||
EOF
|
||||
print $fgccgo $tofgccgo;
|
||||
exit 0;
|
2
vendor/golang.org/x/sys/unix/mksysnum.go
generated
vendored
2
vendor/golang.org/x/sys/unix/mksysnum.go
generated
vendored
|
@ -106,7 +106,7 @@ func main() {
|
|||
|
||||
file := strings.TrimSpace(os.Args[1])
|
||||
var syscalls io.Reader
|
||||
if strings.HasPrefix(file, "http://") {
|
||||
if strings.HasPrefix(file, "https://") || strings.HasPrefix(file, "http://") {
|
||||
// Download syscalls.master file
|
||||
syscalls = fetchFile(file)
|
||||
} else {
|
||||
|
|
2
vendor/golang.org/x/sys/unix/syscall_aix.go
generated
vendored
2
vendor/golang.org/x/sys/unix/syscall_aix.go
generated
vendored
|
@ -227,7 +227,7 @@ func anyToSockaddr(fd int, rsa *RawSockaddrAny) (Sockaddr, error) {
|
|||
|
||||
// Some versions of AIX have a bug in getsockname (see IV78655).
|
||||
// We can't rely on sa.Len being set correctly.
|
||||
n := SizeofSockaddrUnix - 3 // substract leading Family, Len, terminating NUL.
|
||||
n := SizeofSockaddrUnix - 3 // subtract leading Family, Len, terminating NUL.
|
||||
for i := 0; i < n; i++ {
|
||||
if pp.Path[i] == 0 {
|
||||
n = i
|
||||
|
|
1
vendor/golang.org/x/sys/unix/syscall_dragonfly.go
generated
vendored
1
vendor/golang.org/x/sys/unix/syscall_dragonfly.go
generated
vendored
|
@ -304,6 +304,7 @@ func Sendfile(outfd int, infd int, offset *int64, count int) (written int, err e
|
|||
//sys read(fd int, p []byte) (n int, err error)
|
||||
//sys Readlink(path string, buf []byte) (n int, err error)
|
||||
//sys Rename(from string, to string) (err error)
|
||||
//sys Renameat(fromfd int, from string, tofd int, to string) (err error)
|
||||
//sys Revoke(path string) (err error)
|
||||
//sys Rmdir(path string) (err error)
|
||||
//sys Seek(fd int, offset int64, whence int) (newoffset int64, err error) = SYS_LSEEK
|
||||
|
|
17
vendor/golang.org/x/sys/unix/syscall_linux.go
generated
vendored
17
vendor/golang.org/x/sys/unix/syscall_linux.go
generated
vendored
|
@ -14,6 +14,7 @@ package unix
|
|||
import (
|
||||
"encoding/binary"
|
||||
"net"
|
||||
"runtime"
|
||||
"syscall"
|
||||
"unsafe"
|
||||
)
|
||||
|
@ -80,6 +81,12 @@ func ioctlSetTermios(fd int, req uint, value *Termios) error {
|
|||
return ioctl(fd, req, uintptr(unsafe.Pointer(value)))
|
||||
}
|
||||
|
||||
func IoctlSetRTCTime(fd int, value *RTCTime) error {
|
||||
err := ioctl(fd, RTC_SET_TIME, uintptr(unsafe.Pointer(value)))
|
||||
runtime.KeepAlive(value)
|
||||
return err
|
||||
}
|
||||
|
||||
// IoctlGetInt performs an ioctl operation which gets an integer value
|
||||
// from fd, using the specified request number.
|
||||
func IoctlGetInt(fd int, req uint) (int, error) {
|
||||
|
@ -100,6 +107,12 @@ func IoctlGetTermios(fd int, req uint) (*Termios, error) {
|
|||
return &value, err
|
||||
}
|
||||
|
||||
func IoctlGetRTCTime(fd int) (*RTCTime, error) {
|
||||
var value RTCTime
|
||||
err := ioctl(fd, RTC_RD_TIME, uintptr(unsafe.Pointer(&value)))
|
||||
return &value, err
|
||||
}
|
||||
|
||||
//sys Linkat(olddirfd int, oldpath string, newdirfd int, newpath string, flags int) (err error)
|
||||
|
||||
func Link(oldpath string, newpath string) (err error) {
|
||||
|
@ -1381,6 +1394,7 @@ func Sendfile(outfd int, infd int, offset *int64, count int) (written int, err e
|
|||
//sys Chroot(path string) (err error)
|
||||
//sys ClockGetres(clockid int32, res *Timespec) (err error)
|
||||
//sys ClockGettime(clockid int32, time *Timespec) (err error)
|
||||
//sys ClockNanosleep(clockid int32, flags int, request *Timespec, remain *Timespec) (err error)
|
||||
//sys Close(fd int) (err error)
|
||||
//sys CopyFileRange(rfd int, roff *int64, wfd int, woff *int64, len int, flags int) (n int, err error)
|
||||
//sys DeleteModule(name string, flags int) (err error)
|
||||
|
@ -1441,7 +1455,6 @@ func Getpgrp() (pid int) {
|
|||
//sys Pselect(nfd int, r *FdSet, w *FdSet, e *FdSet, timeout *Timespec, sigmask *Sigset_t) (n int, err error) = SYS_PSELECT6
|
||||
//sys read(fd int, p []byte) (n int, err error)
|
||||
//sys Removexattr(path string, attr string) (err error)
|
||||
//sys Renameat(olddirfd int, oldpath string, newdirfd int, newpath string) (err error)
|
||||
//sys Renameat2(olddirfd int, oldpath string, newdirfd int, newpath string, flags uint) (err error)
|
||||
//sys RequestKey(keyType string, description string, callback string, destRingid int) (id int, err error)
|
||||
//sys Setdomainname(p []byte) (err error)
|
||||
|
@ -1466,6 +1479,7 @@ func Setgid(uid int) (err error) {
|
|||
|
||||
//sys Setpriority(which int, who int, prio int) (err error)
|
||||
//sys Setxattr(path string, attr string, data []byte, flags int) (err error)
|
||||
//sys Signalfd(fd int, mask *Sigset_t, flags int) = SYS_SIGNALFD4
|
||||
//sys Statx(dirfd int, path string, flags int, mask int, stat *Statx_t) (err error)
|
||||
//sys Sync()
|
||||
//sys Syncfs(fd int) (err error)
|
||||
|
@ -1682,7 +1696,6 @@ func Faccessat(dirfd int, path string, mode uint32, flags int) (err error) {
|
|||
// Shmdt
|
||||
// Shmget
|
||||
// Sigaltstack
|
||||
// Signalfd
|
||||
// Swapoff
|
||||
// Swapon
|
||||
// Sysfs
|
||||
|
|
1
vendor/golang.org/x/sys/unix/syscall_linux_386.go
generated
vendored
1
vendor/golang.org/x/sys/unix/syscall_linux_386.go
generated
vendored
|
@ -68,6 +68,7 @@ func Pipe2(p []int, flags int) (err error) {
|
|||
//sys Lstat(path string, stat *Stat_t) (err error) = SYS_LSTAT64
|
||||
//sys Pread(fd int, p []byte, offset int64) (n int, err error) = SYS_PREAD64
|
||||
//sys Pwrite(fd int, p []byte, offset int64) (n int, err error) = SYS_PWRITE64
|
||||
//sys Renameat(olddirfd int, oldpath string, newdirfd int, newpath string) (err error)
|
||||
//sys sendfile(outfd int, infd int, offset *int64, count int) (written int, err error) = SYS_SENDFILE64
|
||||
//sys Setfsgid(gid int) (err error) = SYS_SETFSGID32
|
||||
//sys Setfsuid(uid int) (err error) = SYS_SETFSUID32
|
||||
|
|
1
vendor/golang.org/x/sys/unix/syscall_linux_amd64.go
generated
vendored
1
vendor/golang.org/x/sys/unix/syscall_linux_amd64.go
generated
vendored
|
@ -43,6 +43,7 @@ func Lstat(path string, stat *Stat_t) (err error) {
|
|||
//sys Pause() (err error)
|
||||
//sys Pread(fd int, p []byte, offset int64) (n int, err error) = SYS_PREAD64
|
||||
//sys Pwrite(fd int, p []byte, offset int64) (n int, err error) = SYS_PWRITE64
|
||||
//sys Renameat(olddirfd int, oldpath string, newdirfd int, newpath string) (err error)
|
||||
//sys Seek(fd int, offset int64, whence int) (off int64, err error) = SYS_LSEEK
|
||||
|
||||
func Select(nfd int, r *FdSet, w *FdSet, e *FdSet, timeout *Timeval) (n int, err error) {
|
||||
|
|
1
vendor/golang.org/x/sys/unix/syscall_linux_arm.go
generated
vendored
1
vendor/golang.org/x/sys/unix/syscall_linux_arm.go
generated
vendored
|
@ -89,6 +89,7 @@ func Seek(fd int, offset int64, whence int) (newoffset int64, err error) {
|
|||
//sys Listen(s int, n int) (err error)
|
||||
//sys Lstat(path string, stat *Stat_t) (err error) = SYS_LSTAT64
|
||||
//sys Pause() (err error)
|
||||
//sys Renameat(olddirfd int, oldpath string, newdirfd int, newpath string) (err error)
|
||||
//sys sendfile(outfd int, infd int, offset *int64, count int) (written int, err error) = SYS_SENDFILE64
|
||||
//sys Select(nfd int, r *FdSet, w *FdSet, e *FdSet, timeout *Timeval) (n int, err error) = SYS__NEWSELECT
|
||||
//sys Setfsgid(gid int) (err error) = SYS_SETFSGID32
|
||||
|
|
1
vendor/golang.org/x/sys/unix/syscall_linux_arm64.go
generated
vendored
1
vendor/golang.org/x/sys/unix/syscall_linux_arm64.go
generated
vendored
|
@ -30,6 +30,7 @@ func EpollCreate(size int) (fd int, err error) {
|
|||
//sys Listen(s int, n int) (err error)
|
||||
//sys Pread(fd int, p []byte, offset int64) (n int, err error) = SYS_PREAD64
|
||||
//sys Pwrite(fd int, p []byte, offset int64) (n int, err error) = SYS_PWRITE64
|
||||
//sys Renameat(olddirfd int, oldpath string, newdirfd int, newpath string) (err error)
|
||||
//sys Seek(fd int, offset int64, whence int) (off int64, err error) = SYS_LSEEK
|
||||
|
||||
func Select(nfd int, r *FdSet, w *FdSet, e *FdSet, timeout *Timeval) (n int, err error) {
|
||||
|
|
1
vendor/golang.org/x/sys/unix/syscall_linux_mips64x.go
generated
vendored
1
vendor/golang.org/x/sys/unix/syscall_linux_mips64x.go
generated
vendored
|
@ -24,6 +24,7 @@ package unix
|
|||
//sys Pause() (err error)
|
||||
//sys Pread(fd int, p []byte, offset int64) (n int, err error) = SYS_PREAD64
|
||||
//sys Pwrite(fd int, p []byte, offset int64) (n int, err error) = SYS_PWRITE64
|
||||
//sys Renameat(olddirfd int, oldpath string, newdirfd int, newpath string) (err error)
|
||||
//sys Seek(fd int, offset int64, whence int) (off int64, err error) = SYS_LSEEK
|
||||
|
||||
func Select(nfd int, r *FdSet, w *FdSet, e *FdSet, timeout *Timeval) (n int, err error) {
|
||||
|
|
1
vendor/golang.org/x/sys/unix/syscall_linux_mipsx.go
generated
vendored
1
vendor/golang.org/x/sys/unix/syscall_linux_mipsx.go
generated
vendored
|
@ -28,6 +28,7 @@ func Syscall9(trap, a1, a2, a3, a4, a5, a6, a7, a8, a9 uintptr) (r1, r2 uintptr,
|
|||
//sys Listen(s int, n int) (err error)
|
||||
//sys Pread(fd int, p []byte, offset int64) (n int, err error) = SYS_PREAD64
|
||||
//sys Pwrite(fd int, p []byte, offset int64) (n int, err error) = SYS_PWRITE64
|
||||
//sys Renameat(olddirfd int, oldpath string, newdirfd int, newpath string) (err error)
|
||||
//sys Select(nfd int, r *FdSet, w *FdSet, e *FdSet, timeout *Timeval) (n int, err error) = SYS__NEWSELECT
|
||||
//sys sendfile(outfd int, infd int, offset *int64, count int) (written int, err error) = SYS_SENDFILE64
|
||||
//sys Setfsgid(gid int) (err error)
|
||||
|
|
1
vendor/golang.org/x/sys/unix/syscall_linux_ppc64x.go
generated
vendored
1
vendor/golang.org/x/sys/unix/syscall_linux_ppc64x.go
generated
vendored
|
@ -30,6 +30,7 @@ package unix
|
|||
//sys Pause() (err error)
|
||||
//sys Pread(fd int, p []byte, offset int64) (n int, err error) = SYS_PREAD64
|
||||
//sys Pwrite(fd int, p []byte, offset int64) (n int, err error) = SYS_PWRITE64
|
||||
//sys Renameat(olddirfd int, oldpath string, newdirfd int, newpath string) (err error)
|
||||
//sys Seek(fd int, offset int64, whence int) (off int64, err error) = SYS_LSEEK
|
||||
//sys Select(nfd int, r *FdSet, w *FdSet, e *FdSet, timeout *Timeval) (n int, err error) = SYS__NEWSELECT
|
||||
//sys sendfile(outfd int, infd int, offset *int64, count int) (written int, err error)
|
||||
|
|
4
vendor/golang.org/x/sys/unix/syscall_linux_riscv64.go
generated
vendored
4
vendor/golang.org/x/sys/unix/syscall_linux_riscv64.go
generated
vendored
|
@ -207,3 +207,7 @@ func Poll(fds []PollFd, timeout int) (n int, err error) {
|
|||
}
|
||||
return ppoll(&fds[0], len(fds), ts, nil)
|
||||
}
|
||||
|
||||
func Renameat(olddirfd int, oldpath string, newdirfd int, newpath string) (err error) {
|
||||
return Renameat2(olddirfd, oldpath, newdirfd, newpath, 0)
|
||||
}
|
||||
|
|
1
vendor/golang.org/x/sys/unix/syscall_linux_s390x.go
generated
vendored
1
vendor/golang.org/x/sys/unix/syscall_linux_s390x.go
generated
vendored
|
@ -30,6 +30,7 @@ import (
|
|||
//sys Pause() (err error)
|
||||
//sys Pread(fd int, p []byte, offset int64) (n int, err error) = SYS_PREAD64
|
||||
//sys Pwrite(fd int, p []byte, offset int64) (n int, err error) = SYS_PWRITE64
|
||||
//sys Renameat(olddirfd int, oldpath string, newdirfd int, newpath string) (err error)
|
||||
//sys Seek(fd int, offset int64, whence int) (off int64, err error) = SYS_LSEEK
|
||||
//sys Select(nfd int, r *FdSet, w *FdSet, e *FdSet, timeout *Timeval) (n int, err error)
|
||||
//sys sendfile(outfd int, infd int, offset *int64, count int) (written int, err error)
|
||||
|
|
1
vendor/golang.org/x/sys/unix/syscall_linux_sparc64.go
generated
vendored
1
vendor/golang.org/x/sys/unix/syscall_linux_sparc64.go
generated
vendored
|
@ -26,6 +26,7 @@ package unix
|
|||
//sys Pause() (err error)
|
||||
//sys Pread(fd int, p []byte, offset int64) (n int, err error) = SYS_PREAD64
|
||||
//sys Pwrite(fd int, p []byte, offset int64) (n int, err error) = SYS_PWRITE64
|
||||
//sys Renameat(olddirfd int, oldpath string, newdirfd int, newpath string) (err error)
|
||||
//sys Seek(fd int, offset int64, whence int) (off int64, err error) = SYS_LSEEK
|
||||
//sys Select(nfd int, r *FdSet, w *FdSet, e *FdSet, timeout *Timeval) (n int, err error)
|
||||
//sys sendfile(outfd int, infd int, offset *int64, count int) (written int, err error)
|
||||
|
|
33
vendor/golang.org/x/sys/unix/syscall_netbsd_arm64.go
generated
vendored
Normal file
33
vendor/golang.org/x/sys/unix/syscall_netbsd_arm64.go
generated
vendored
Normal file
|
@ -0,0 +1,33 @@
|
|||
// Copyright 2019 The Go Authors. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
// +build arm64,netbsd
|
||||
|
||||
package unix
|
||||
|
||||
func setTimespec(sec, nsec int64) Timespec {
|
||||
return Timespec{Sec: sec, Nsec: nsec}
|
||||
}
|
||||
|
||||
func setTimeval(sec, usec int64) Timeval {
|
||||
return Timeval{Sec: sec, Usec: int32(usec)}
|
||||
}
|
||||
|
||||
func SetKevent(k *Kevent_t, fd, mode, flags int) {
|
||||
k.Ident = uint64(fd)
|
||||
k.Filter = uint32(mode)
|
||||
k.Flags = uint32(flags)
|
||||
}
|
||||
|
||||
func (iov *Iovec) SetLen(length int) {
|
||||
iov.Len = uint64(length)
|
||||
}
|
||||
|
||||
func (msghdr *Msghdr) SetControllen(length int) {
|
||||
msghdr.Controllen = uint32(length)
|
||||
}
|
||||
|
||||
func (cmsg *Cmsghdr) SetLen(length int) {
|
||||
cmsg.Len = uint32(length)
|
||||
}
|
Some files were not shown because too many files have changed in this diff Show more
Loading…
Reference in a new issue