Help us shape the future of Go! Take the 2026 Go Developer Survey

brenda

package module
v1.1.0 Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: Mar 23, 2017 License: MIT Imports: 8 Imported by: 18

README

Build Status Go Report Card codecov

Brenda

Brenda is a boolean expression solver.

Given an AST expression containing an arbitrary combination of !, && and || expressions, it is possible to solve the boolean state of certain components. For example:

printExample(`
	var a bool
	if a {} else {}
`)
// Output:
// if a {
// 	// a TRUE
// } else {
// 	// a FALSE
// }

Some inputs may be unknown:

printExample(`
	var a, b, c bool
	if a && (b || c) {} else if b {}
`)
// Output:
// if a && (b || c) {
// 	// a TRUE
// 	// b UNKNOWN
// 	// c UNKNOWN
// } else if b {
// 	// a FALSE
// 	// b TRUE
// 	// c UNKNOWN
// }

Some branches may be impossible:

printExample(`
	var a bool
	if a {} else if !a {} else {}
`)
// Output:
// if a {
// 	// a TRUE
// } else if !a {
// 	// a FALSE
// } else {
// 	// IMPOSSIBLE
// }

Brenda supports complex components, and can detect the inverse use of ==, !=, <, >=, > and <=:

printExample(`
	var a error
	var b, c bool
	var d int
	if a == nil && (b && d > 0) || c {} else if d <= 0 || c {} else if b {}
`)
// Output:
// if a == nil && (b && d > 0) || c {
// 	// a == nil UNKNOWN
// 	// b UNKNOWN
// 	// c UNKNOWN
// 	// d > 0 UNKNOWN
// } else if d <= 0 || c {
// 	// a == nil UNKNOWN
// 	// b UNKNOWN
// 	// c FALSE
// 	// d <= 0 TRUE
// } else if b {
// 	// a == nil FALSE
// 	// b TRUE
// 	// c FALSE
// 	// d > 0 TRUE
// }

Here's an example of the full usage:

// A simple source file
src := `package foo

func foo(a, b bool) {
	if a { } else if b { } else { }
}`

// We parse the AST
fset := token.NewFileSet()
f, err := parser.ParseFile(fset, "foo.go", src, 0)
if err != nil {
	fmt.Println(err)
	return
}

// We extract type info
info := &types.Info{Uses: make(map[*ast.Ident]types.Object)}
conf := types.Config{Importer: importer.Default()}
if _, err = conf.Check("foo", fset, []*ast.File{f}, info); err != nil {
	fmt.Println(err)
	return
}

// Walk the AST until we find the first *ast.IfStmt
var ifs *ast.IfStmt
ast.Inspect(f, func(node ast.Node) bool {
	if ifs != nil {
		return false
	}
	if n, ok := node.(*ast.IfStmt); ok && n != nil {
		ifs = n
		return false
	}
	return true
})
if ifs == nil {
	fmt.Println("No *ast.IfStmt found")
	return
}

var printIf func(*ast.IfStmt, ...ast.Expr) error
var sprintResults func(*brenda.Solver) string
var sprintNode func(ast.Node) string

// This is called recursively for the if and all else-if statements. falseExpr
// is a slice of all the conditions that came before an else-if statement,
// which must all be false for the else-if to be reached.
printIf = func(ifStmt *ast.IfStmt, falseExpr ...ast.Expr) error {

	s := brenda.NewSolver(fset, info.Uses, ifStmt.Cond, falseExpr...)
	err := s.SolveTrue()
	if err != nil {
		return err
	}

	fmt.Printf("if %s {\n%s\n}", sprintNode(ifStmt.Cond), sprintResults(s))

	switch e := ifStmt.Else.(type) {
	case *ast.BlockStmt:

		// Else block
		s := brenda.NewSolver(fset, info.Uses, ifStmt.Cond, falseExpr...)
		s.SolveFalse()

		fmt.Printf(" else {\n%s\n}", sprintResults(s))

	case *ast.IfStmt:

		// Else if statement
		fmt.Print(" else ")

		// Add the condition from the current if statement to the list of
		// false expressions for the else-if solver
		falseExpr = append(falseExpr, ifStmt.Cond)
		printIf(e, falseExpr...)

	}
	return nil
}

// Helper function to print results
sprintResults = func(s *brenda.Solver) string {
	if s.Impossible {
		// If the expression is impossible
		return "\t// IMPOSSIBLE"
	}

	// The results must be sorted to ensure repeatable output
	var lines []string
	for expr, result := range s.Components {
		switch {
		case result.Match:
			lines = append(lines, fmt.Sprint("\t// ", printNode(fset, expr), " TRUE"))
		case result.Inverse:
			lines = append(lines, fmt.Sprint("\t// ", printNode(fset, expr), " FALSE"))
		default:
			lines = append(lines, fmt.Sprint("\t// ", printNode(fset, expr), " UNKNOWN"))
		}
	}
	sort.Strings(lines)
	return strings.Join(lines, "\n")
}

// Helper function to print AST nodes
sprintNode = func(n ast.Node) string {
	buf := &bytes.Buffer{}
	err := format.Node(buf, fset, n)
	if err != nil {
		return err.Error()
	}
	return buf.String()
}

if err := printIf(ifs); err != nil {
	fmt.Println(err)
	return
}

// Output:
// if a {
// 	// a TRUE
// } else if b {
// 	// a FALSE
// 	// b TRUE
// } else {
// 	// a FALSE
// 	// b FALSE
// }

Documentation

Overview

Package brenda is a boolean expression solver for Go AST

Index

Examples

Constants

This section is empty.

Variables

This section is empty.

Functions

This section is empty.

Types

type Result

type Result struct {
	Match   bool // Match is true if this component must be true.
	Inverse bool // Inverse is true if this component must be false.
}

Result contains information about each result.

type Solver

type Solver struct {
	Components map[ast.Expr]*Result // Components is a map of all the individual components of the expression, and the results
	Impossible bool                 // Impossible is true if this expression is impossible
	// contains filtered or unexported fields
}

Solver solves boolean expressions given the ast.Expr

func NewSolver

func NewSolver(fset *token.FileSet, uses, defs map[*ast.Ident]types.Object, expression ast.Expr, falseExpressions ...ast.Expr) *Solver

NewSolver returns a new *Solver. fset should be the AST FileSet. uses should be the Uses from go/types.Info. expression is the expression to solve. falseExpressions is a slice of expressions we know to be false - e.g. all previous conditions that came before an else-if statement.

Example (And)
package main

import (
	"bytes"
	"go/ast"
	"go/format"
	"go/importer"
	"go/parser"
	"go/token"
	"go/types"
	"io"

	"github.com/dave/brenda"

	"fmt"

	"sort"
	"strings"

	"github.com/pkg/errors"

	"os"
)

func main() {
	printExample(`
		var a, b bool
		if a && b {} else {}
	`)
}

func printExample(src string) {
	err := printOutput(os.Stdout, src)
	if err != nil {
		fmt.Printf("%+v", err)
	}
}

func printOutput(writer io.Writer, src string) error {
	fpath := "/foo.go"
	ppath := "a.b/c"
	src = fmt.Sprintf("package a\n\nfunc a() {\n%s\n}", src)

	fset := token.NewFileSet()
	f, err := parser.ParseFile(fset, fpath, src, parser.ParseComments)
	if err != nil {
		return errors.Wrap(err, "Error parsing file")
	}

	info := types.Info{
		Uses: make(map[*ast.Ident]types.Object),
		Defs: make(map[*ast.Ident]types.Object),
	}
	conf := types.Config{
		Importer: importer.Default(),
	}
	if _, err = conf.Check(ppath, fset, []*ast.File{f}, &info); err != nil {
		return errors.Wrap(err, "Error checking conf")
	}

	var ifs *ast.IfStmt
	ast.Inspect(f, func(node ast.Node) bool {
		if ifs != nil {

			return false
		}
		if n, ok := node.(*ast.IfStmt); ok && n != nil {
			ifs = n
			return false
		}
		return true
	})
	if ifs == nil {
		return errors.New("No *ast.IfStmt found")
	}

	err = printIf(writer, fset, info, ifs)
	if err != nil {
		return err
	}

	return nil
}

func printIf(writer io.Writer, fset *token.FileSet, info types.Info, expr *ast.IfStmt, falseExpr ...ast.Expr) error {

	s := brenda.NewSolver(fset, info.Uses, info.Defs, expr.Cond, falseExpr...)
	err := s.SolveTrue()
	if err != nil {
		return err
	}

	fmt.Fprintln(writer, "if", printNode(fset, expr.Cond), "{")

	printMatches(writer, fset, s)

	switch e := expr.Else.(type) {
	case nil:

		fmt.Fprintln(writer, "}")
	case *ast.BlockStmt:

		s := brenda.NewSolver(fset, info.Uses, info.Defs, expr.Cond, falseExpr...)
		s.SolveFalse()
		fmt.Fprintln(writer, "} else {")
		printMatches(writer, fset, s)
		fmt.Fprintln(writer, "}")
	case *ast.IfStmt:

		fmt.Fprint(writer, "} else ")
		falseExpr = append(falseExpr, expr.Cond)
		printIf(writer, fset, info, e, falseExpr...)
	}
	return nil
}

func printMatches(writer io.Writer, fset *token.FileSet, s *brenda.Solver) {
	if s.Impossible {
		fmt.Fprintln(writer, "\t// IMPOSSIBLE")
		return
	}
	var lines []string
	for ex, m := range s.Components {
		switch {
		case m.Match:
			lines = append(lines, fmt.Sprint("\t// ", printNode(fset, ex), " TRUE"))
		case m.Inverse:
			lines = append(lines, fmt.Sprint("\t// ", printNode(fset, ex), " FALSE"))
		default:
			lines = append(lines, fmt.Sprint("\t// ", printNode(fset, ex), " UNKNOWN"))
		}
	}
	sort.Strings(lines)
	fmt.Fprintln(writer, strings.Join(lines, "\n"))
}

func printNode(fset *token.FileSet, node ast.Node) string {
	buf := &bytes.Buffer{}
	err := format.Node(buf, fset, node)
	if err != nil {
		return err.Error()
	}
	return buf.String()
}
Output:
if a && b {
	// a TRUE
	// b TRUE
} else {
	// a UNKNOWN
	// b UNKNOWN
}
Example (Bool_lit_1)
package main

import (
	"bytes"
	"go/ast"
	"go/format"
	"go/importer"
	"go/parser"
	"go/token"
	"go/types"
	"io"

	"github.com/dave/brenda"

	"fmt"

	"sort"
	"strings"

	"github.com/pkg/errors"

	"os"
)

func main() {
	printExample(`
		if false {} else {}
	`)
}

func printExample(src string) {
	err := printOutput(os.Stdout, src)
	if err != nil {
		fmt.Printf("%+v", err)
	}
}

func printOutput(writer io.Writer, src string) error {
	fpath := "/foo.go"
	ppath := "a.b/c"
	src = fmt.Sprintf("package a\n\nfunc a() {\n%s\n}", src)

	fset := token.NewFileSet()
	f, err := parser.ParseFile(fset, fpath, src, parser.ParseComments)
	if err != nil {
		return errors.Wrap(err, "Error parsing file")
	}

	info := types.Info{
		Uses: make(map[*ast.Ident]types.Object),
		Defs: make(map[*ast.Ident]types.Object),
	}
	conf := types.Config{
		Importer: importer.Default(),
	}
	if _, err = conf.Check(ppath, fset, []*ast.File{f}, &info); err != nil {
		return errors.Wrap(err, "Error checking conf")
	}

	var ifs *ast.IfStmt
	ast.Inspect(f, func(node ast.Node) bool {
		if ifs != nil {

			return false
		}
		if n, ok := node.(*ast.IfStmt); ok && n != nil {
			ifs = n
			return false
		}
		return true
	})
	if ifs == nil {
		return errors.New("No *ast.IfStmt found")
	}

	err = printIf(writer, fset, info, ifs)
	if err != nil {
		return err
	}

	return nil
}

func printIf(writer io.Writer, fset *token.FileSet, info types.Info, expr *ast.IfStmt, falseExpr ...ast.Expr) error {

	s := brenda.NewSolver(fset, info.Uses, info.Defs, expr.Cond, falseExpr...)
	err := s.SolveTrue()
	if err != nil {
		return err
	}

	fmt.Fprintln(writer, "if", printNode(fset, expr.Cond), "{")

	printMatches(writer, fset, s)

	switch e := expr.Else.(type) {
	case nil:

		fmt.Fprintln(writer, "}")
	case *ast.BlockStmt:

		s := brenda.NewSolver(fset, info.Uses, info.Defs, expr.Cond, falseExpr...)
		s.SolveFalse()
		fmt.Fprintln(writer, "} else {")
		printMatches(writer, fset, s)
		fmt.Fprintln(writer, "}")
	case *ast.IfStmt:

		fmt.Fprint(writer, "} else ")
		falseExpr = append(falseExpr, expr.Cond)
		printIf(writer, fset, info, e, falseExpr...)
	}
	return nil
}

func printMatches(writer io.Writer, fset *token.FileSet, s *brenda.Solver) {
	if s.Impossible {
		fmt.Fprintln(writer, "\t// IMPOSSIBLE")
		return
	}
	var lines []string
	for ex, m := range s.Components {
		switch {
		case m.Match:
			lines = append(lines, fmt.Sprint("\t// ", printNode(fset, ex), " TRUE"))
		case m.Inverse:
			lines = append(lines, fmt.Sprint("\t// ", printNode(fset, ex), " FALSE"))
		default:
			lines = append(lines, fmt.Sprint("\t// ", printNode(fset, ex), " UNKNOWN"))
		}
	}
	sort.Strings(lines)
	fmt.Fprintln(writer, strings.Join(lines, "\n"))
}

func printNode(fset *token.FileSet, node ast.Node) string {
	buf := &bytes.Buffer{}
	err := format.Node(buf, fset, node)
	if err != nil {
		return err.Error()
	}
	return buf.String()
}
Output:
if false {
	// IMPOSSIBLE
} else {

}
Example (Bool_lit_2)
package main

import (
	"bytes"
	"go/ast"
	"go/format"
	"go/importer"
	"go/parser"
	"go/token"
	"go/types"
	"io"

	"github.com/dave/brenda"

	"fmt"

	"sort"
	"strings"

	"github.com/pkg/errors"

	"os"
)

func main() {
	printExample(`
		var a bool
		if a || true {} else {}
	`)
}

func printExample(src string) {
	err := printOutput(os.Stdout, src)
	if err != nil {
		fmt.Printf("%+v", err)
	}
}

func printOutput(writer io.Writer, src string) error {
	fpath := "/foo.go"
	ppath := "a.b/c"
	src = fmt.Sprintf("package a\n\nfunc a() {\n%s\n}", src)

	fset := token.NewFileSet()
	f, err := parser.ParseFile(fset, fpath, src, parser.ParseComments)
	if err != nil {
		return errors.Wrap(err, "Error parsing file")
	}

	info := types.Info{
		Uses: make(map[*ast.Ident]types.Object),
		Defs: make(map[*ast.Ident]types.Object),
	}
	conf := types.Config{
		Importer: importer.Default(),
	}
	if _, err = conf.Check(ppath, fset, []*ast.File{f}, &info); err != nil {
		return errors.Wrap(err, "Error checking conf")
	}

	var ifs *ast.IfStmt
	ast.Inspect(f, func(node ast.Node) bool {
		if ifs != nil {

			return false
		}
		if n, ok := node.(*ast.IfStmt); ok && n != nil {
			ifs = n
			return false
		}
		return true
	})
	if ifs == nil {
		return errors.New("No *ast.IfStmt found")
	}

	err = printIf(writer, fset, info, ifs)
	if err != nil {
		return err
	}

	return nil
}

func printIf(writer io.Writer, fset *token.FileSet, info types.Info, expr *ast.IfStmt, falseExpr ...ast.Expr) error {

	s := brenda.NewSolver(fset, info.Uses, info.Defs, expr.Cond, falseExpr...)
	err := s.SolveTrue()
	if err != nil {
		return err
	}

	fmt.Fprintln(writer, "if", printNode(fset, expr.Cond), "{")

	printMatches(writer, fset, s)

	switch e := expr.Else.(type) {
	case nil:

		fmt.Fprintln(writer, "}")
	case *ast.BlockStmt:

		s := brenda.NewSolver(fset, info.Uses, info.Defs, expr.Cond, falseExpr...)
		s.SolveFalse()
		fmt.Fprintln(writer, "} else {")
		printMatches(writer, fset, s)
		fmt.Fprintln(writer, "}")
	case *ast.IfStmt:

		fmt.Fprint(writer, "} else ")
		falseExpr = append(falseExpr, expr.Cond)
		printIf(writer, fset, info, e, falseExpr...)
	}
	return nil
}

func printMatches(writer io.Writer, fset *token.FileSet, s *brenda.Solver) {
	if s.Impossible {
		fmt.Fprintln(writer, "\t// IMPOSSIBLE")
		return
	}
	var lines []string
	for ex, m := range s.Components {
		switch {
		case m.Match:
			lines = append(lines, fmt.Sprint("\t// ", printNode(fset, ex), " TRUE"))
		case m.Inverse:
			lines = append(lines, fmt.Sprint("\t// ", printNode(fset, ex), " FALSE"))
		default:
			lines = append(lines, fmt.Sprint("\t// ", printNode(fset, ex), " UNKNOWN"))
		}
	}
	sort.Strings(lines)
	fmt.Fprintln(writer, strings.Join(lines, "\n"))
}

func printNode(fset *token.FileSet, node ast.Node) string {
	buf := &bytes.Buffer{}
	err := format.Node(buf, fset, node)
	if err != nil {
		return err.Error()
	}
	return buf.String()
}
Output:
if a || true {
	// a UNKNOWN
} else {
	// IMPOSSIBLE
}
Example (Bool_lit_3)
package main

import (
	"bytes"
	"go/ast"
	"go/format"
	"go/importer"
	"go/parser"
	"go/token"
	"go/types"
	"io"

	"github.com/dave/brenda"

	"fmt"

	"sort"
	"strings"

	"github.com/pkg/errors"

	"os"
)

func main() {
	printExample(`
		var a bool
		if a && false {} else {}
	`)
}

func printExample(src string) {
	err := printOutput(os.Stdout, src)
	if err != nil {
		fmt.Printf("%+v", err)
	}
}

func printOutput(writer io.Writer, src string) error {
	fpath := "/foo.go"
	ppath := "a.b/c"
	src = fmt.Sprintf("package a\n\nfunc a() {\n%s\n}", src)

	fset := token.NewFileSet()
	f, err := parser.ParseFile(fset, fpath, src, parser.ParseComments)
	if err != nil {
		return errors.Wrap(err, "Error parsing file")
	}

	info := types.Info{
		Uses: make(map[*ast.Ident]types.Object),
		Defs: make(map[*ast.Ident]types.Object),
	}
	conf := types.Config{
		Importer: importer.Default(),
	}
	if _, err = conf.Check(ppath, fset, []*ast.File{f}, &info); err != nil {
		return errors.Wrap(err, "Error checking conf")
	}

	var ifs *ast.IfStmt
	ast.Inspect(f, func(node ast.Node) bool {
		if ifs != nil {

			return false
		}
		if n, ok := node.(*ast.IfStmt); ok && n != nil {
			ifs = n
			return false
		}
		return true
	})
	if ifs == nil {
		return errors.New("No *ast.IfStmt found")
	}

	err = printIf(writer, fset, info, ifs)
	if err != nil {
		return err
	}

	return nil
}

func printIf(writer io.Writer, fset *token.FileSet, info types.Info, expr *ast.IfStmt, falseExpr ...ast.Expr) error {

	s := brenda.NewSolver(fset, info.Uses, info.Defs, expr.Cond, falseExpr...)
	err := s.SolveTrue()
	if err != nil {
		return err
	}

	fmt.Fprintln(writer, "if", printNode(fset, expr.Cond), "{")

	printMatches(writer, fset, s)

	switch e := expr.Else.(type) {
	case nil:

		fmt.Fprintln(writer, "}")
	case *ast.BlockStmt:

		s := brenda.NewSolver(fset, info.Uses, info.Defs, expr.Cond, falseExpr...)
		s.SolveFalse()
		fmt.Fprintln(writer, "} else {")
		printMatches(writer, fset, s)
		fmt.Fprintln(writer, "}")
	case *ast.IfStmt:

		fmt.Fprint(writer, "} else ")
		falseExpr = append(falseExpr, expr.Cond)
		printIf(writer, fset, info, e, falseExpr...)
	}
	return nil
}

func printMatches(writer io.Writer, fset *token.FileSet, s *brenda.Solver) {
	if s.Impossible {
		fmt.Fprintln(writer, "\t// IMPOSSIBLE")
		return
	}
	var lines []string
	for ex, m := range s.Components {
		switch {
		case m.Match:
			lines = append(lines, fmt.Sprint("\t// ", printNode(fset, ex), " TRUE"))
		case m.Inverse:
			lines = append(lines, fmt.Sprint("\t// ", printNode(fset, ex), " FALSE"))
		default:
			lines = append(lines, fmt.Sprint("\t// ", printNode(fset, ex), " UNKNOWN"))
		}
	}
	sort.Strings(lines)
	fmt.Fprintln(writer, strings.Join(lines, "\n"))
}

func printNode(fset *token.FileSet, node ast.Node) string {
	buf := &bytes.Buffer{}
	err := format.Node(buf, fset, node)
	if err != nil {
		return err.Error()
	}
	return buf.String()
}
Output:
if a && false {
	// IMPOSSIBLE
} else {
	// a UNKNOWN
}
Example (Brackets1)
package main

import (
	"bytes"
	"go/ast"
	"go/format"
	"go/importer"
	"go/parser"
	"go/token"
	"go/types"
	"io"

	"github.com/dave/brenda"

	"fmt"

	"sort"
	"strings"

	"github.com/pkg/errors"

	"os"
)

func main() {
	printExample(`
		var a, b, c bool
		if a || (b && c) {} else if a {} else {}
	`)
}

func printExample(src string) {
	err := printOutput(os.Stdout, src)
	if err != nil {
		fmt.Printf("%+v", err)
	}
}

func printOutput(writer io.Writer, src string) error {
	fpath := "/foo.go"
	ppath := "a.b/c"
	src = fmt.Sprintf("package a\n\nfunc a() {\n%s\n}", src)

	fset := token.NewFileSet()
	f, err := parser.ParseFile(fset, fpath, src, parser.ParseComments)
	if err != nil {
		return errors.Wrap(err, "Error parsing file")
	}

	info := types.Info{
		Uses: make(map[*ast.Ident]types.Object),
		Defs: make(map[*ast.Ident]types.Object),
	}
	conf := types.Config{
		Importer: importer.Default(),
	}
	if _, err = conf.Check(ppath, fset, []*ast.File{f}, &info); err != nil {
		return errors.Wrap(err, "Error checking conf")
	}

	var ifs *ast.IfStmt
	ast.Inspect(f, func(node ast.Node) bool {
		if ifs != nil {

			return false
		}
		if n, ok := node.(*ast.IfStmt); ok && n != nil {
			ifs = n
			return false
		}
		return true
	})
	if ifs == nil {
		return errors.New("No *ast.IfStmt found")
	}

	err = printIf(writer, fset, info, ifs)
	if err != nil {
		return err
	}

	return nil
}

func printIf(writer io.Writer, fset *token.FileSet, info types.Info, expr *ast.IfStmt, falseExpr ...ast.Expr) error {

	s := brenda.NewSolver(fset, info.Uses, info.Defs, expr.Cond, falseExpr...)
	err := s.SolveTrue()
	if err != nil {
		return err
	}

	fmt.Fprintln(writer, "if", printNode(fset, expr.Cond), "{")

	printMatches(writer, fset, s)

	switch e := expr.Else.(type) {
	case nil:

		fmt.Fprintln(writer, "}")
	case *ast.BlockStmt:

		s := brenda.NewSolver(fset, info.Uses, info.Defs, expr.Cond, falseExpr...)
		s.SolveFalse()
		fmt.Fprintln(writer, "} else {")
		printMatches(writer, fset, s)
		fmt.Fprintln(writer, "}")
	case *ast.IfStmt:

		fmt.Fprint(writer, "} else ")
		falseExpr = append(falseExpr, expr.Cond)
		printIf(writer, fset, info, e, falseExpr...)
	}
	return nil
}

func printMatches(writer io.Writer, fset *token.FileSet, s *brenda.Solver) {
	if s.Impossible {
		fmt.Fprintln(writer, "\t// IMPOSSIBLE")
		return
	}
	var lines []string
	for ex, m := range s.Components {
		switch {
		case m.Match:
			lines = append(lines, fmt.Sprint("\t// ", printNode(fset, ex), " TRUE"))
		case m.Inverse:
			lines = append(lines, fmt.Sprint("\t// ", printNode(fset, ex), " FALSE"))
		default:
			lines = append(lines, fmt.Sprint("\t// ", printNode(fset, ex), " UNKNOWN"))
		}
	}
	sort.Strings(lines)
	fmt.Fprintln(writer, strings.Join(lines, "\n"))
}

func printNode(fset *token.FileSet, node ast.Node) string {
	buf := &bytes.Buffer{}
	err := format.Node(buf, fset, node)
	if err != nil {
		return err.Error()
	}
	return buf.String()
}
Output:
if a || (b && c) {
	// a UNKNOWN
	// b UNKNOWN
	// c UNKNOWN
} else if a {
	// IMPOSSIBLE
} else {
	// a FALSE
	// b UNKNOWN
	// c UNKNOWN
}
Example (Brackets2)
package main

import (
	"bytes"
	"go/ast"
	"go/format"
	"go/importer"
	"go/parser"
	"go/token"
	"go/types"
	"io"

	"github.com/dave/brenda"

	"fmt"

	"sort"
	"strings"

	"github.com/pkg/errors"

	"os"
)

func main() {
	printExample(`
		var a, b, c bool
		if a || (b && c) {} else if b {} else {}
	`)
}

func printExample(src string) {
	err := printOutput(os.Stdout, src)
	if err != nil {
		fmt.Printf("%+v", err)
	}
}

func printOutput(writer io.Writer, src string) error {
	fpath := "/foo.go"
	ppath := "a.b/c"
	src = fmt.Sprintf("package a\n\nfunc a() {\n%s\n}", src)

	fset := token.NewFileSet()
	f, err := parser.ParseFile(fset, fpath, src, parser.ParseComments)
	if err != nil {
		return errors.Wrap(err, "Error parsing file")
	}

	info := types.Info{
		Uses: make(map[*ast.Ident]types.Object),
		Defs: make(map[*ast.Ident]types.Object),
	}
	conf := types.Config{
		Importer: importer.Default(),
	}
	if _, err = conf.Check(ppath, fset, []*ast.File{f}, &info); err != nil {
		return errors.Wrap(err, "Error checking conf")
	}

	var ifs *ast.IfStmt
	ast.Inspect(f, func(node ast.Node) bool {
		if ifs != nil {

			return false
		}
		if n, ok := node.(*ast.IfStmt); ok && n != nil {
			ifs = n
			return false
		}
		return true
	})
	if ifs == nil {
		return errors.New("No *ast.IfStmt found")
	}

	err = printIf(writer, fset, info, ifs)
	if err != nil {
		return err
	}

	return nil
}

func printIf(writer io.Writer, fset *token.FileSet, info types.Info, expr *ast.IfStmt, falseExpr ...ast.Expr) error {

	s := brenda.NewSolver(fset, info.Uses, info.Defs, expr.Cond, falseExpr...)
	err := s.SolveTrue()
	if err != nil {
		return err
	}

	fmt.Fprintln(writer, "if", printNode(fset, expr.Cond), "{")

	printMatches(writer, fset, s)

	switch e := expr.Else.(type) {
	case nil:

		fmt.Fprintln(writer, "}")
	case *ast.BlockStmt:

		s := brenda.NewSolver(fset, info.Uses, info.Defs, expr.Cond, falseExpr...)
		s.SolveFalse()
		fmt.Fprintln(writer, "} else {")
		printMatches(writer, fset, s)
		fmt.Fprintln(writer, "}")
	case *ast.IfStmt:

		fmt.Fprint(writer, "} else ")
		falseExpr = append(falseExpr, expr.Cond)
		printIf(writer, fset, info, e, falseExpr...)
	}
	return nil
}

func printMatches(writer io.Writer, fset *token.FileSet, s *brenda.Solver) {
	if s.Impossible {
		fmt.Fprintln(writer, "\t// IMPOSSIBLE")
		return
	}
	var lines []string
	for ex, m := range s.Components {
		switch {
		case m.Match:
			lines = append(lines, fmt.Sprint("\t// ", printNode(fset, ex), " TRUE"))
		case m.Inverse:
			lines = append(lines, fmt.Sprint("\t// ", printNode(fset, ex), " FALSE"))
		default:
			lines = append(lines, fmt.Sprint("\t// ", printNode(fset, ex), " UNKNOWN"))
		}
	}
	sort.Strings(lines)
	fmt.Fprintln(writer, strings.Join(lines, "\n"))
}

func printNode(fset *token.FileSet, node ast.Node) string {
	buf := &bytes.Buffer{}
	err := format.Node(buf, fset, node)
	if err != nil {
		return err.Error()
	}
	return buf.String()
}
Output:
if a || (b && c) {
	// a UNKNOWN
	// b UNKNOWN
	// c UNKNOWN
} else if b {
	// a FALSE
	// b TRUE
	// c FALSE
} else {
	// a FALSE
	// b FALSE
	// c UNKNOWN
}
Example (Brackets3)
package main

import (
	"bytes"
	"go/ast"
	"go/format"
	"go/importer"
	"go/parser"
	"go/token"
	"go/types"
	"io"

	"github.com/dave/brenda"

	"fmt"

	"sort"
	"strings"

	"github.com/pkg/errors"

	"os"
)

func main() {
	printExample(`
		var a, b, c bool
		if a && (b || c) {} else if a {} else {}
	`)
}

func printExample(src string) {
	err := printOutput(os.Stdout, src)
	if err != nil {
		fmt.Printf("%+v", err)
	}
}

func printOutput(writer io.Writer, src string) error {
	fpath := "/foo.go"
	ppath := "a.b/c"
	src = fmt.Sprintf("package a\n\nfunc a() {\n%s\n}", src)

	fset := token.NewFileSet()
	f, err := parser.ParseFile(fset, fpath, src, parser.ParseComments)
	if err != nil {
		return errors.Wrap(err, "Error parsing file")
	}

	info := types.Info{
		Uses: make(map[*ast.Ident]types.Object),
		Defs: make(map[*ast.Ident]types.Object),
	}
	conf := types.Config{
		Importer: importer.Default(),
	}
	if _, err = conf.Check(ppath, fset, []*ast.File{f}, &info); err != nil {
		return errors.Wrap(err, "Error checking conf")
	}

	var ifs *ast.IfStmt
	ast.Inspect(f, func(node ast.Node) bool {
		if ifs != nil {

			return false
		}
		if n, ok := node.(*ast.IfStmt); ok && n != nil {
			ifs = n
			return false
		}
		return true
	})
	if ifs == nil {
		return errors.New("No *ast.IfStmt found")
	}

	err = printIf(writer, fset, info, ifs)
	if err != nil {
		return err
	}

	return nil
}

func printIf(writer io.Writer, fset *token.FileSet, info types.Info, expr *ast.IfStmt, falseExpr ...ast.Expr) error {

	s := brenda.NewSolver(fset, info.Uses, info.Defs, expr.Cond, falseExpr...)
	err := s.SolveTrue()
	if err != nil {
		return err
	}

	fmt.Fprintln(writer, "if", printNode(fset, expr.Cond), "{")

	printMatches(writer, fset, s)

	switch e := expr.Else.(type) {
	case nil:

		fmt.Fprintln(writer, "}")
	case *ast.BlockStmt:

		s := brenda.NewSolver(fset, info.Uses, info.Defs, expr.Cond, falseExpr...)
		s.SolveFalse()
		fmt.Fprintln(writer, "} else {")
		printMatches(writer, fset, s)
		fmt.Fprintln(writer, "}")
	case *ast.IfStmt:

		fmt.Fprint(writer, "} else ")
		falseExpr = append(falseExpr, expr.Cond)
		printIf(writer, fset, info, e, falseExpr...)
	}
	return nil
}

func printMatches(writer io.Writer, fset *token.FileSet, s *brenda.Solver) {
	if s.Impossible {
		fmt.Fprintln(writer, "\t// IMPOSSIBLE")
		return
	}
	var lines []string
	for ex, m := range s.Components {
		switch {
		case m.Match:
			lines = append(lines, fmt.Sprint("\t// ", printNode(fset, ex), " TRUE"))
		case m.Inverse:
			lines = append(lines, fmt.Sprint("\t// ", printNode(fset, ex), " FALSE"))
		default:
			lines = append(lines, fmt.Sprint("\t// ", printNode(fset, ex), " UNKNOWN"))
		}
	}
	sort.Strings(lines)
	fmt.Fprintln(writer, strings.Join(lines, "\n"))
}

func printNode(fset *token.FileSet, node ast.Node) string {
	buf := &bytes.Buffer{}
	err := format.Node(buf, fset, node)
	if err != nil {
		return err.Error()
	}
	return buf.String()
}
Output:
if a && (b || c) {
	// a TRUE
	// b UNKNOWN
	// c UNKNOWN
} else if a {
	// a TRUE
	// b FALSE
	// c FALSE
} else {
	// a FALSE
	// b UNKNOWN
	// c UNKNOWN
}
Example (Brackets4)
package main

import (
	"bytes"
	"go/ast"
	"go/format"
	"go/importer"
	"go/parser"
	"go/token"
	"go/types"
	"io"

	"github.com/dave/brenda"

	"fmt"

	"sort"
	"strings"

	"github.com/pkg/errors"

	"os"
)

func main() {
	printExample(`
		var a, b, c bool
		if a && (b || c) {} else if b {} else {}
	`)
}

func printExample(src string) {
	err := printOutput(os.Stdout, src)
	if err != nil {
		fmt.Printf("%+v", err)
	}
}

func printOutput(writer io.Writer, src string) error {
	fpath := "/foo.go"
	ppath := "a.b/c"
	src = fmt.Sprintf("package a\n\nfunc a() {\n%s\n}", src)

	fset := token.NewFileSet()
	f, err := parser.ParseFile(fset, fpath, src, parser.ParseComments)
	if err != nil {
		return errors.Wrap(err, "Error parsing file")
	}

	info := types.Info{
		Uses: make(map[*ast.Ident]types.Object),
		Defs: make(map[*ast.Ident]types.Object),
	}
	conf := types.Config{
		Importer: importer.Default(),
	}
	if _, err = conf.Check(ppath, fset, []*ast.File{f}, &info); err != nil {
		return errors.Wrap(err, "Error checking conf")
	}

	var ifs *ast.IfStmt
	ast.Inspect(f, func(node ast.Node) bool {
		if ifs != nil {

			return false
		}
		if n, ok := node.(*ast.IfStmt); ok && n != nil {
			ifs = n
			return false
		}
		return true
	})
	if ifs == nil {
		return errors.New("No *ast.IfStmt found")
	}

	err = printIf(writer, fset, info, ifs)
	if err != nil {
		return err
	}

	return nil
}

func printIf(writer io.Writer, fset *token.FileSet, info types.Info, expr *ast.IfStmt, falseExpr ...ast.Expr) error {

	s := brenda.NewSolver(fset, info.Uses, info.Defs, expr.Cond, falseExpr...)
	err := s.SolveTrue()
	if err != nil {
		return err
	}

	fmt.Fprintln(writer, "if", printNode(fset, expr.Cond), "{")

	printMatches(writer, fset, s)

	switch e := expr.Else.(type) {
	case nil:

		fmt.Fprintln(writer, "}")
	case *ast.BlockStmt:

		s := brenda.NewSolver(fset, info.Uses, info.Defs, expr.Cond, falseExpr...)
		s.SolveFalse()
		fmt.Fprintln(writer, "} else {")
		printMatches(writer, fset, s)
		fmt.Fprintln(writer, "}")
	case *ast.IfStmt:

		fmt.Fprint(writer, "} else ")
		falseExpr = append(falseExpr, expr.Cond)
		printIf(writer, fset, info, e, falseExpr...)
	}
	return nil
}

func printMatches(writer io.Writer, fset *token.FileSet, s *brenda.Solver) {
	if s.Impossible {
		fmt.Fprintln(writer, "\t// IMPOSSIBLE")
		return
	}
	var lines []string
	for ex, m := range s.Components {
		switch {
		case m.Match:
			lines = append(lines, fmt.Sprint("\t// ", printNode(fset, ex), " TRUE"))
		case m.Inverse:
			lines = append(lines, fmt.Sprint("\t// ", printNode(fset, ex), " FALSE"))
		default:
			lines = append(lines, fmt.Sprint("\t// ", printNode(fset, ex), " UNKNOWN"))
		}
	}
	sort.Strings(lines)
	fmt.Fprintln(writer, strings.Join(lines, "\n"))
}

func printNode(fset *token.FileSet, node ast.Node) string {
	buf := &bytes.Buffer{}
	err := format.Node(buf, fset, node)
	if err != nil {
		return err.Error()
	}
	return buf.String()
}
Output:
if a && (b || c) {
	// a TRUE
	// b UNKNOWN
	// c UNKNOWN
} else if b {
	// a FALSE
	// b TRUE
	// c UNKNOWN
} else {
	// a UNKNOWN
	// b FALSE
	// c UNKNOWN
}
Example (Else)
package main

import (
	"bytes"
	"go/ast"
	"go/format"
	"go/importer"
	"go/parser"
	"go/token"
	"go/types"
	"io"

	"github.com/dave/brenda"

	"fmt"

	"sort"
	"strings"

	"github.com/pkg/errors"

	"os"
)

func main() {
	printExample(`
		var a bool
		if a {} else {}
	`)
}

func printExample(src string) {
	err := printOutput(os.Stdout, src)
	if err != nil {
		fmt.Printf("%+v", err)
	}
}

func printOutput(writer io.Writer, src string) error {
	fpath := "/foo.go"
	ppath := "a.b/c"
	src = fmt.Sprintf("package a\n\nfunc a() {\n%s\n}", src)

	fset := token.NewFileSet()
	f, err := parser.ParseFile(fset, fpath, src, parser.ParseComments)
	if err != nil {
		return errors.Wrap(err, "Error parsing file")
	}

	info := types.Info{
		Uses: make(map[*ast.Ident]types.Object),
		Defs: make(map[*ast.Ident]types.Object),
	}
	conf := types.Config{
		Importer: importer.Default(),
	}
	if _, err = conf.Check(ppath, fset, []*ast.File{f}, &info); err != nil {
		return errors.Wrap(err, "Error checking conf")
	}

	var ifs *ast.IfStmt
	ast.Inspect(f, func(node ast.Node) bool {
		if ifs != nil {

			return false
		}
		if n, ok := node.(*ast.IfStmt); ok && n != nil {
			ifs = n
			return false
		}
		return true
	})
	if ifs == nil {
		return errors.New("No *ast.IfStmt found")
	}

	err = printIf(writer, fset, info, ifs)
	if err != nil {
		return err
	}

	return nil
}

func printIf(writer io.Writer, fset *token.FileSet, info types.Info, expr *ast.IfStmt, falseExpr ...ast.Expr) error {

	s := brenda.NewSolver(fset, info.Uses, info.Defs, expr.Cond, falseExpr...)
	err := s.SolveTrue()
	if err != nil {
		return err
	}

	fmt.Fprintln(writer, "if", printNode(fset, expr.Cond), "{")

	printMatches(writer, fset, s)

	switch e := expr.Else.(type) {
	case nil:

		fmt.Fprintln(writer, "}")
	case *ast.BlockStmt:

		s := brenda.NewSolver(fset, info.Uses, info.Defs, expr.Cond, falseExpr...)
		s.SolveFalse()
		fmt.Fprintln(writer, "} else {")
		printMatches(writer, fset, s)
		fmt.Fprintln(writer, "}")
	case *ast.IfStmt:

		fmt.Fprint(writer, "} else ")
		falseExpr = append(falseExpr, expr.Cond)
		printIf(writer, fset, info, e, falseExpr...)
	}
	return nil
}

func printMatches(writer io.Writer, fset *token.FileSet, s *brenda.Solver) {
	if s.Impossible {
		fmt.Fprintln(writer, "\t// IMPOSSIBLE")
		return
	}
	var lines []string
	for ex, m := range s.Components {
		switch {
		case m.Match:
			lines = append(lines, fmt.Sprint("\t// ", printNode(fset, ex), " TRUE"))
		case m.Inverse:
			lines = append(lines, fmt.Sprint("\t// ", printNode(fset, ex), " FALSE"))
		default:
			lines = append(lines, fmt.Sprint("\t// ", printNode(fset, ex), " UNKNOWN"))
		}
	}
	sort.Strings(lines)
	fmt.Fprintln(writer, strings.Join(lines, "\n"))
}

func printNode(fset *token.FileSet, node ast.Node) string {
	buf := &bytes.Buffer{}
	err := format.Node(buf, fset, node)
	if err != nil {
		return err.Error()
	}
	return buf.String()
}
Output:
if a {
	// a TRUE
} else {
	// a FALSE
}
Example (Else_if)
package main

import (
	"bytes"
	"go/ast"
	"go/format"
	"go/importer"
	"go/parser"
	"go/token"
	"go/types"
	"io"

	"github.com/dave/brenda"

	"fmt"

	"sort"
	"strings"

	"github.com/pkg/errors"

	"os"
)

func main() {
	printExample(`
		var a, b bool
		if a {} else if b {} else {}
	`)
}

func printExample(src string) {
	err := printOutput(os.Stdout, src)
	if err != nil {
		fmt.Printf("%+v", err)
	}
}

func printOutput(writer io.Writer, src string) error {
	fpath := "/foo.go"
	ppath := "a.b/c"
	src = fmt.Sprintf("package a\n\nfunc a() {\n%s\n}", src)

	fset := token.NewFileSet()
	f, err := parser.ParseFile(fset, fpath, src, parser.ParseComments)
	if err != nil {
		return errors.Wrap(err, "Error parsing file")
	}

	info := types.Info{
		Uses: make(map[*ast.Ident]types.Object),
		Defs: make(map[*ast.Ident]types.Object),
	}
	conf := types.Config{
		Importer: importer.Default(),
	}
	if _, err = conf.Check(ppath, fset, []*ast.File{f}, &info); err != nil {
		return errors.Wrap(err, "Error checking conf")
	}

	var ifs *ast.IfStmt
	ast.Inspect(f, func(node ast.Node) bool {
		if ifs != nil {

			return false
		}
		if n, ok := node.(*ast.IfStmt); ok && n != nil {
			ifs = n
			return false
		}
		return true
	})
	if ifs == nil {
		return errors.New("No *ast.IfStmt found")
	}

	err = printIf(writer, fset, info, ifs)
	if err != nil {
		return err
	}

	return nil
}

func printIf(writer io.Writer, fset *token.FileSet, info types.Info, expr *ast.IfStmt, falseExpr ...ast.Expr) error {

	s := brenda.NewSolver(fset, info.Uses, info.Defs, expr.Cond, falseExpr...)
	err := s.SolveTrue()
	if err != nil {
		return err
	}

	fmt.Fprintln(writer, "if", printNode(fset, expr.Cond), "{")

	printMatches(writer, fset, s)

	switch e := expr.Else.(type) {
	case nil:

		fmt.Fprintln(writer, "}")
	case *ast.BlockStmt:

		s := brenda.NewSolver(fset, info.Uses, info.Defs, expr.Cond, falseExpr...)
		s.SolveFalse()
		fmt.Fprintln(writer, "} else {")
		printMatches(writer, fset, s)
		fmt.Fprintln(writer, "}")
	case *ast.IfStmt:

		fmt.Fprint(writer, "} else ")
		falseExpr = append(falseExpr, expr.Cond)
		printIf(writer, fset, info, e, falseExpr...)
	}
	return nil
}

func printMatches(writer io.Writer, fset *token.FileSet, s *brenda.Solver) {
	if s.Impossible {
		fmt.Fprintln(writer, "\t// IMPOSSIBLE")
		return
	}
	var lines []string
	for ex, m := range s.Components {
		switch {
		case m.Match:
			lines = append(lines, fmt.Sprint("\t// ", printNode(fset, ex), " TRUE"))
		case m.Inverse:
			lines = append(lines, fmt.Sprint("\t// ", printNode(fset, ex), " FALSE"))
		default:
			lines = append(lines, fmt.Sprint("\t// ", printNode(fset, ex), " UNKNOWN"))
		}
	}
	sort.Strings(lines)
	fmt.Fprintln(writer, strings.Join(lines, "\n"))
}

func printNode(fset *token.FileSet, node ast.Node) string {
	buf := &bytes.Buffer{}
	err := format.Node(buf, fset, node)
	if err != nil {
		return err.Error()
	}
	return buf.String()
}
Output:
if a {
	// a TRUE
} else if b {
	// a FALSE
	// b TRUE
} else {
	// a FALSE
	// b FALSE
}
Example (Errors)
package main

import (
	"bytes"
	"go/ast"
	"go/format"
	"go/importer"
	"go/parser"
	"go/token"
	"go/types"
	"io"

	"github.com/dave/brenda"

	"fmt"

	"sort"
	"strings"

	"github.com/pkg/errors"

	"os"
)

func main() {
	printExample(`
		var a error
		if a == nil {} else {}
	`)
}

func printExample(src string) {
	err := printOutput(os.Stdout, src)
	if err != nil {
		fmt.Printf("%+v", err)
	}
}

func printOutput(writer io.Writer, src string) error {
	fpath := "/foo.go"
	ppath := "a.b/c"
	src = fmt.Sprintf("package a\n\nfunc a() {\n%s\n}", src)

	fset := token.NewFileSet()
	f, err := parser.ParseFile(fset, fpath, src, parser.ParseComments)
	if err != nil {
		return errors.Wrap(err, "Error parsing file")
	}

	info := types.Info{
		Uses: make(map[*ast.Ident]types.Object),
		Defs: make(map[*ast.Ident]types.Object),
	}
	conf := types.Config{
		Importer: importer.Default(),
	}
	if _, err = conf.Check(ppath, fset, []*ast.File{f}, &info); err != nil {
		return errors.Wrap(err, "Error checking conf")
	}

	var ifs *ast.IfStmt
	ast.Inspect(f, func(node ast.Node) bool {
		if ifs != nil {

			return false
		}
		if n, ok := node.(*ast.IfStmt); ok && n != nil {
			ifs = n
			return false
		}
		return true
	})
	if ifs == nil {
		return errors.New("No *ast.IfStmt found")
	}

	err = printIf(writer, fset, info, ifs)
	if err != nil {
		return err
	}

	return nil
}

func printIf(writer io.Writer, fset *token.FileSet, info types.Info, expr *ast.IfStmt, falseExpr ...ast.Expr) error {

	s := brenda.NewSolver(fset, info.Uses, info.Defs, expr.Cond, falseExpr...)
	err := s.SolveTrue()
	if err != nil {
		return err
	}

	fmt.Fprintln(writer, "if", printNode(fset, expr.Cond), "{")

	printMatches(writer, fset, s)

	switch e := expr.Else.(type) {
	case nil:

		fmt.Fprintln(writer, "}")
	case *ast.BlockStmt:

		s := brenda.NewSolver(fset, info.Uses, info.Defs, expr.Cond, falseExpr...)
		s.SolveFalse()
		fmt.Fprintln(writer, "} else {")
		printMatches(writer, fset, s)
		fmt.Fprintln(writer, "}")
	case *ast.IfStmt:

		fmt.Fprint(writer, "} else ")
		falseExpr = append(falseExpr, expr.Cond)
		printIf(writer, fset, info, e, falseExpr...)
	}
	return nil
}

func printMatches(writer io.Writer, fset *token.FileSet, s *brenda.Solver) {
	if s.Impossible {
		fmt.Fprintln(writer, "\t// IMPOSSIBLE")
		return
	}
	var lines []string
	for ex, m := range s.Components {
		switch {
		case m.Match:
			lines = append(lines, fmt.Sprint("\t// ", printNode(fset, ex), " TRUE"))
		case m.Inverse:
			lines = append(lines, fmt.Sprint("\t// ", printNode(fset, ex), " FALSE"))
		default:
			lines = append(lines, fmt.Sprint("\t// ", printNode(fset, ex), " UNKNOWN"))
		}
	}
	sort.Strings(lines)
	fmt.Fprintln(writer, strings.Join(lines, "\n"))
}

func printNode(fset *token.FileSet, node ast.Node) string {
	buf := &bytes.Buffer{}
	err := format.Node(buf, fset, node)
	if err != nil {
		return err.Error()
	}
	return buf.String()
}
Output:
if a == nil {
	// a == nil TRUE
} else {
	// a != nil TRUE
}
Example (Impossible)
package main

import (
	"bytes"
	"go/ast"
	"go/format"
	"go/importer"
	"go/parser"
	"go/token"
	"go/types"
	"io"

	"github.com/dave/brenda"

	"fmt"

	"sort"
	"strings"

	"github.com/pkg/errors"

	"os"
)

func main() {
	printExample(`
		var a bool
		if a {} else if !a {} else {}
	`)
}

func printExample(src string) {
	err := printOutput(os.Stdout, src)
	if err != nil {
		fmt.Printf("%+v", err)
	}
}

func printOutput(writer io.Writer, src string) error {
	fpath := "/foo.go"
	ppath := "a.b/c"
	src = fmt.Sprintf("package a\n\nfunc a() {\n%s\n}", src)

	fset := token.NewFileSet()
	f, err := parser.ParseFile(fset, fpath, src, parser.ParseComments)
	if err != nil {
		return errors.Wrap(err, "Error parsing file")
	}

	info := types.Info{
		Uses: make(map[*ast.Ident]types.Object),
		Defs: make(map[*ast.Ident]types.Object),
	}
	conf := types.Config{
		Importer: importer.Default(),
	}
	if _, err = conf.Check(ppath, fset, []*ast.File{f}, &info); err != nil {
		return errors.Wrap(err, "Error checking conf")
	}

	var ifs *ast.IfStmt
	ast.Inspect(f, func(node ast.Node) bool {
		if ifs != nil {

			return false
		}
		if n, ok := node.(*ast.IfStmt); ok && n != nil {
			ifs = n
			return false
		}
		return true
	})
	if ifs == nil {
		return errors.New("No *ast.IfStmt found")
	}

	err = printIf(writer, fset, info, ifs)
	if err != nil {
		return err
	}

	return nil
}

func printIf(writer io.Writer, fset *token.FileSet, info types.Info, expr *ast.IfStmt, falseExpr ...ast.Expr) error {

	s := brenda.NewSolver(fset, info.Uses, info.Defs, expr.Cond, falseExpr...)
	err := s.SolveTrue()
	if err != nil {
		return err
	}

	fmt.Fprintln(writer, "if", printNode(fset, expr.Cond), "{")

	printMatches(writer, fset, s)

	switch e := expr.Else.(type) {
	case nil:

		fmt.Fprintln(writer, "}")
	case *ast.BlockStmt:

		s := brenda.NewSolver(fset, info.Uses, info.Defs, expr.Cond, falseExpr...)
		s.SolveFalse()
		fmt.Fprintln(writer, "} else {")
		printMatches(writer, fset, s)
		fmt.Fprintln(writer, "}")
	case *ast.IfStmt:

		fmt.Fprint(writer, "} else ")
		falseExpr = append(falseExpr, expr.Cond)
		printIf(writer, fset, info, e, falseExpr...)
	}
	return nil
}

func printMatches(writer io.Writer, fset *token.FileSet, s *brenda.Solver) {
	if s.Impossible {
		fmt.Fprintln(writer, "\t// IMPOSSIBLE")
		return
	}
	var lines []string
	for ex, m := range s.Components {
		switch {
		case m.Match:
			lines = append(lines, fmt.Sprint("\t// ", printNode(fset, ex), " TRUE"))
		case m.Inverse:
			lines = append(lines, fmt.Sprint("\t// ", printNode(fset, ex), " FALSE"))
		default:
			lines = append(lines, fmt.Sprint("\t// ", printNode(fset, ex), " UNKNOWN"))
		}
	}
	sort.Strings(lines)
	fmt.Fprintln(writer, strings.Join(lines, "\n"))
}

func printNode(fset *token.FileSet, node ast.Node) string {
	buf := &bytes.Buffer{}
	err := format.Node(buf, fset, node)
	if err != nil {
		return err.Error()
	}
	return buf.String()
}
Output:
if a {
	// a TRUE
} else if !a {
	// a FALSE
} else {
	// IMPOSSIBLE
}
Example (Invert)
package main

import (
	"bytes"
	"go/ast"
	"go/format"
	"go/importer"
	"go/parser"
	"go/token"
	"go/types"
	"io"

	"github.com/dave/brenda"

	"fmt"

	"sort"
	"strings"

	"github.com/pkg/errors"

	"os"
)

func main() {
	printExample(`
		// should correctly detect that b == nil is the inverse of b != nil
		var a, b error
		if a == nil && b == nil {} else if b != nil {} else {}
	`)
}

func printExample(src string) {
	err := printOutput(os.Stdout, src)
	if err != nil {
		fmt.Printf("%+v", err)
	}
}

func printOutput(writer io.Writer, src string) error {
	fpath := "/foo.go"
	ppath := "a.b/c"
	src = fmt.Sprintf("package a\n\nfunc a() {\n%s\n}", src)

	fset := token.NewFileSet()
	f, err := parser.ParseFile(fset, fpath, src, parser.ParseComments)
	if err != nil {
		return errors.Wrap(err, "Error parsing file")
	}

	info := types.Info{
		Uses: make(map[*ast.Ident]types.Object),
		Defs: make(map[*ast.Ident]types.Object),
	}
	conf := types.Config{
		Importer: importer.Default(),
	}
	if _, err = conf.Check(ppath, fset, []*ast.File{f}, &info); err != nil {
		return errors.Wrap(err, "Error checking conf")
	}

	var ifs *ast.IfStmt
	ast.Inspect(f, func(node ast.Node) bool {
		if ifs != nil {

			return false
		}
		if n, ok := node.(*ast.IfStmt); ok && n != nil {
			ifs = n
			return false
		}
		return true
	})
	if ifs == nil {
		return errors.New("No *ast.IfStmt found")
	}

	err = printIf(writer, fset, info, ifs)
	if err != nil {
		return err
	}

	return nil
}

func printIf(writer io.Writer, fset *token.FileSet, info types.Info, expr *ast.IfStmt, falseExpr ...ast.Expr) error {

	s := brenda.NewSolver(fset, info.Uses, info.Defs, expr.Cond, falseExpr...)
	err := s.SolveTrue()
	if err != nil {
		return err
	}

	fmt.Fprintln(writer, "if", printNode(fset, expr.Cond), "{")

	printMatches(writer, fset, s)

	switch e := expr.Else.(type) {
	case nil:

		fmt.Fprintln(writer, "}")
	case *ast.BlockStmt:

		s := brenda.NewSolver(fset, info.Uses, info.Defs, expr.Cond, falseExpr...)
		s.SolveFalse()
		fmt.Fprintln(writer, "} else {")
		printMatches(writer, fset, s)
		fmt.Fprintln(writer, "}")
	case *ast.IfStmt:

		fmt.Fprint(writer, "} else ")
		falseExpr = append(falseExpr, expr.Cond)
		printIf(writer, fset, info, e, falseExpr...)
	}
	return nil
}

func printMatches(writer io.Writer, fset *token.FileSet, s *brenda.Solver) {
	if s.Impossible {
		fmt.Fprintln(writer, "\t// IMPOSSIBLE")
		return
	}
	var lines []string
	for ex, m := range s.Components {
		switch {
		case m.Match:
			lines = append(lines, fmt.Sprint("\t// ", printNode(fset, ex), " TRUE"))
		case m.Inverse:
			lines = append(lines, fmt.Sprint("\t// ", printNode(fset, ex), " FALSE"))
		default:
			lines = append(lines, fmt.Sprint("\t// ", printNode(fset, ex), " UNKNOWN"))
		}
	}
	sort.Strings(lines)
	fmt.Fprintln(writer, strings.Join(lines, "\n"))
}

func printNode(fset *token.FileSet, node ast.Node) string {
	buf := &bytes.Buffer{}
	err := format.Node(buf, fset, node)
	if err != nil {
		return err.Error()
	}
	return buf.String()
}
Output:
if a == nil && b == nil {
	// a == nil TRUE
	// b == nil TRUE
} else if b != nil {
	// a == nil UNKNOWN
	// b != nil TRUE
} else {
	// a == nil FALSE
	// b == nil TRUE
}
Example (Invert2)
package main

import (
	"bytes"
	"go/ast"
	"go/format"
	"go/importer"
	"go/parser"
	"go/token"
	"go/types"
	"io"

	"github.com/dave/brenda"

	"fmt"

	"sort"
	"strings"

	"github.com/pkg/errors"

	"os"
)

func main() {
	printExample(`
		// should correctly detect that a == nil is the inverse of a != nil
		var a error
		var b bool
		if a == nil && b || a != nil {} else if b {} else {}
	`)
}

func printExample(src string) {
	err := printOutput(os.Stdout, src)
	if err != nil {
		fmt.Printf("%+v", err)
	}
}

func printOutput(writer io.Writer, src string) error {
	fpath := "/foo.go"
	ppath := "a.b/c"
	src = fmt.Sprintf("package a\n\nfunc a() {\n%s\n}", src)

	fset := token.NewFileSet()
	f, err := parser.ParseFile(fset, fpath, src, parser.ParseComments)
	if err != nil {
		return errors.Wrap(err, "Error parsing file")
	}

	info := types.Info{
		Uses: make(map[*ast.Ident]types.Object),
		Defs: make(map[*ast.Ident]types.Object),
	}
	conf := types.Config{
		Importer: importer.Default(),
	}
	if _, err = conf.Check(ppath, fset, []*ast.File{f}, &info); err != nil {
		return errors.Wrap(err, "Error checking conf")
	}

	var ifs *ast.IfStmt
	ast.Inspect(f, func(node ast.Node) bool {
		if ifs != nil {

			return false
		}
		if n, ok := node.(*ast.IfStmt); ok && n != nil {
			ifs = n
			return false
		}
		return true
	})
	if ifs == nil {
		return errors.New("No *ast.IfStmt found")
	}

	err = printIf(writer, fset, info, ifs)
	if err != nil {
		return err
	}

	return nil
}

func printIf(writer io.Writer, fset *token.FileSet, info types.Info, expr *ast.IfStmt, falseExpr ...ast.Expr) error {

	s := brenda.NewSolver(fset, info.Uses, info.Defs, expr.Cond, falseExpr...)
	err := s.SolveTrue()
	if err != nil {
		return err
	}

	fmt.Fprintln(writer, "if", printNode(fset, expr.Cond), "{")

	printMatches(writer, fset, s)

	switch e := expr.Else.(type) {
	case nil:

		fmt.Fprintln(writer, "}")
	case *ast.BlockStmt:

		s := brenda.NewSolver(fset, info.Uses, info.Defs, expr.Cond, falseExpr...)
		s.SolveFalse()
		fmt.Fprintln(writer, "} else {")
		printMatches(writer, fset, s)
		fmt.Fprintln(writer, "}")
	case *ast.IfStmt:

		fmt.Fprint(writer, "} else ")
		falseExpr = append(falseExpr, expr.Cond)
		printIf(writer, fset, info, e, falseExpr...)
	}
	return nil
}

func printMatches(writer io.Writer, fset *token.FileSet, s *brenda.Solver) {
	if s.Impossible {
		fmt.Fprintln(writer, "\t// IMPOSSIBLE")
		return
	}
	var lines []string
	for ex, m := range s.Components {
		switch {
		case m.Match:
			lines = append(lines, fmt.Sprint("\t// ", printNode(fset, ex), " TRUE"))
		case m.Inverse:
			lines = append(lines, fmt.Sprint("\t// ", printNode(fset, ex), " FALSE"))
		default:
			lines = append(lines, fmt.Sprint("\t// ", printNode(fset, ex), " UNKNOWN"))
		}
	}
	sort.Strings(lines)
	fmt.Fprintln(writer, strings.Join(lines, "\n"))
}

func printNode(fset *token.FileSet, node ast.Node) string {
	buf := &bytes.Buffer{}
	err := format.Node(buf, fset, node)
	if err != nil {
		return err.Error()
	}
	return buf.String()
}
Output:
if a == nil && b || a != nil {
	// a == nil UNKNOWN
	// b UNKNOWN
} else if b {
	// IMPOSSIBLE
} else {
	// a == nil TRUE
	// b FALSE
}
Example (Invert_gt)
package main

import (
	"bytes"
	"go/ast"
	"go/format"
	"go/importer"
	"go/parser"
	"go/token"
	"go/types"
	"io"

	"github.com/dave/brenda"

	"fmt"

	"sort"
	"strings"

	"github.com/pkg/errors"

	"os"
)

func main() {
	printExample(`
		var a int
		if a > 0 {} else if a <= 0 {} else {}
	`)
}

func printExample(src string) {
	err := printOutput(os.Stdout, src)
	if err != nil {
		fmt.Printf("%+v", err)
	}
}

func printOutput(writer io.Writer, src string) error {
	fpath := "/foo.go"
	ppath := "a.b/c"
	src = fmt.Sprintf("package a\n\nfunc a() {\n%s\n}", src)

	fset := token.NewFileSet()
	f, err := parser.ParseFile(fset, fpath, src, parser.ParseComments)
	if err != nil {
		return errors.Wrap(err, "Error parsing file")
	}

	info := types.Info{
		Uses: make(map[*ast.Ident]types.Object),
		Defs: make(map[*ast.Ident]types.Object),
	}
	conf := types.Config{
		Importer: importer.Default(),
	}
	if _, err = conf.Check(ppath, fset, []*ast.File{f}, &info); err != nil {
		return errors.Wrap(err, "Error checking conf")
	}

	var ifs *ast.IfStmt
	ast.Inspect(f, func(node ast.Node) bool {
		if ifs != nil {

			return false
		}
		if n, ok := node.(*ast.IfStmt); ok && n != nil {
			ifs = n
			return false
		}
		return true
	})
	if ifs == nil {
		return errors.New("No *ast.IfStmt found")
	}

	err = printIf(writer, fset, info, ifs)
	if err != nil {
		return err
	}

	return nil
}

func printIf(writer io.Writer, fset *token.FileSet, info types.Info, expr *ast.IfStmt, falseExpr ...ast.Expr) error {

	s := brenda.NewSolver(fset, info.Uses, info.Defs, expr.Cond, falseExpr...)
	err := s.SolveTrue()
	if err != nil {
		return err
	}

	fmt.Fprintln(writer, "if", printNode(fset, expr.Cond), "{")

	printMatches(writer, fset, s)

	switch e := expr.Else.(type) {
	case nil:

		fmt.Fprintln(writer, "}")
	case *ast.BlockStmt:

		s := brenda.NewSolver(fset, info.Uses, info.Defs, expr.Cond, falseExpr...)
		s.SolveFalse()
		fmt.Fprintln(writer, "} else {")
		printMatches(writer, fset, s)
		fmt.Fprintln(writer, "}")
	case *ast.IfStmt:

		fmt.Fprint(writer, "} else ")
		falseExpr = append(falseExpr, expr.Cond)
		printIf(writer, fset, info, e, falseExpr...)
	}
	return nil
}

func printMatches(writer io.Writer, fset *token.FileSet, s *brenda.Solver) {
	if s.Impossible {
		fmt.Fprintln(writer, "\t// IMPOSSIBLE")
		return
	}
	var lines []string
	for ex, m := range s.Components {
		switch {
		case m.Match:
			lines = append(lines, fmt.Sprint("\t// ", printNode(fset, ex), " TRUE"))
		case m.Inverse:
			lines = append(lines, fmt.Sprint("\t// ", printNode(fset, ex), " FALSE"))
		default:
			lines = append(lines, fmt.Sprint("\t// ", printNode(fset, ex), " UNKNOWN"))
		}
	}
	sort.Strings(lines)
	fmt.Fprintln(writer, strings.Join(lines, "\n"))
}

func printNode(fset *token.FileSet, node ast.Node) string {
	buf := &bytes.Buffer{}
	err := format.Node(buf, fset, node)
	if err != nil {
		return err.Error()
	}
	return buf.String()
}
Output:
if a > 0 {
	// a > 0 TRUE
} else if a <= 0 {
	// a <= 0 TRUE
} else {
	// IMPOSSIBLE
}
Example (Invert_lt)
package main

import (
	"bytes"
	"go/ast"
	"go/format"
	"go/importer"
	"go/parser"
	"go/token"
	"go/types"
	"io"

	"github.com/dave/brenda"

	"fmt"

	"sort"
	"strings"

	"github.com/pkg/errors"

	"os"
)

func main() {
	printExample(`
		var a int
		if a < 0 {} else if a >= 0 {} else {}
	`)
}

func printExample(src string) {
	err := printOutput(os.Stdout, src)
	if err != nil {
		fmt.Printf("%+v", err)
	}
}

func printOutput(writer io.Writer, src string) error {
	fpath := "/foo.go"
	ppath := "a.b/c"
	src = fmt.Sprintf("package a\n\nfunc a() {\n%s\n}", src)

	fset := token.NewFileSet()
	f, err := parser.ParseFile(fset, fpath, src, parser.ParseComments)
	if err != nil {
		return errors.Wrap(err, "Error parsing file")
	}

	info := types.Info{
		Uses: make(map[*ast.Ident]types.Object),
		Defs: make(map[*ast.Ident]types.Object),
	}
	conf := types.Config{
		Importer: importer.Default(),
	}
	if _, err = conf.Check(ppath, fset, []*ast.File{f}, &info); err != nil {
		return errors.Wrap(err, "Error checking conf")
	}

	var ifs *ast.IfStmt
	ast.Inspect(f, func(node ast.Node) bool {
		if ifs != nil {

			return false
		}
		if n, ok := node.(*ast.IfStmt); ok && n != nil {
			ifs = n
			return false
		}
		return true
	})
	if ifs == nil {
		return errors.New("No *ast.IfStmt found")
	}

	err = printIf(writer, fset, info, ifs)
	if err != nil {
		return err
	}

	return nil
}

func printIf(writer io.Writer, fset *token.FileSet, info types.Info, expr *ast.IfStmt, falseExpr ...ast.Expr) error {

	s := brenda.NewSolver(fset, info.Uses, info.Defs, expr.Cond, falseExpr...)
	err := s.SolveTrue()
	if err != nil {
		return err
	}

	fmt.Fprintln(writer, "if", printNode(fset, expr.Cond), "{")

	printMatches(writer, fset, s)

	switch e := expr.Else.(type) {
	case nil:

		fmt.Fprintln(writer, "}")
	case *ast.BlockStmt:

		s := brenda.NewSolver(fset, info.Uses, info.Defs, expr.Cond, falseExpr...)
		s.SolveFalse()
		fmt.Fprintln(writer, "} else {")
		printMatches(writer, fset, s)
		fmt.Fprintln(writer, "}")
	case *ast.IfStmt:

		fmt.Fprint(writer, "} else ")
		falseExpr = append(falseExpr, expr.Cond)
		printIf(writer, fset, info, e, falseExpr...)
	}
	return nil
}

func printMatches(writer io.Writer, fset *token.FileSet, s *brenda.Solver) {
	if s.Impossible {
		fmt.Fprintln(writer, "\t// IMPOSSIBLE")
		return
	}
	var lines []string
	for ex, m := range s.Components {
		switch {
		case m.Match:
			lines = append(lines, fmt.Sprint("\t// ", printNode(fset, ex), " TRUE"))
		case m.Inverse:
			lines = append(lines, fmt.Sprint("\t// ", printNode(fset, ex), " FALSE"))
		default:
			lines = append(lines, fmt.Sprint("\t// ", printNode(fset, ex), " UNKNOWN"))
		}
	}
	sort.Strings(lines)
	fmt.Fprintln(writer, strings.Join(lines, "\n"))
}

func printNode(fset *token.FileSet, node ast.Node) string {
	buf := &bytes.Buffer{}
	err := format.Node(buf, fset, node)
	if err != nil {
		return err.Error()
	}
	return buf.String()
}
Output:
if a < 0 {
	// a < 0 TRUE
} else if a >= 0 {
	// a >= 0 TRUE
} else {
	// IMPOSSIBLE
}
Example (Mixed)
package main

import (
	"bytes"
	"go/ast"
	"go/format"
	"go/importer"
	"go/parser"
	"go/token"
	"go/types"
	"io"

	"github.com/dave/brenda"

	"fmt"

	"sort"
	"strings"

	"github.com/pkg/errors"

	"os"
)

func main() {
	printExample(`
		var a error
		var b, c bool
		var d int
		if a == nil && (b && d > 0) || c {} else if d <= 0 || c {} else if b {}
	`)
}

func printExample(src string) {
	err := printOutput(os.Stdout, src)
	if err != nil {
		fmt.Printf("%+v", err)
	}
}

func printOutput(writer io.Writer, src string) error {
	fpath := "/foo.go"
	ppath := "a.b/c"
	src = fmt.Sprintf("package a\n\nfunc a() {\n%s\n}", src)

	fset := token.NewFileSet()
	f, err := parser.ParseFile(fset, fpath, src, parser.ParseComments)
	if err != nil {
		return errors.Wrap(err, "Error parsing file")
	}

	info := types.Info{
		Uses: make(map[*ast.Ident]types.Object),
		Defs: make(map[*ast.Ident]types.Object),
	}
	conf := types.Config{
		Importer: importer.Default(),
	}
	if _, err = conf.Check(ppath, fset, []*ast.File{f}, &info); err != nil {
		return errors.Wrap(err, "Error checking conf")
	}

	var ifs *ast.IfStmt
	ast.Inspect(f, func(node ast.Node) bool {
		if ifs != nil {

			return false
		}
		if n, ok := node.(*ast.IfStmt); ok && n != nil {
			ifs = n
			return false
		}
		return true
	})
	if ifs == nil {
		return errors.New("No *ast.IfStmt found")
	}

	err = printIf(writer, fset, info, ifs)
	if err != nil {
		return err
	}

	return nil
}

func printIf(writer io.Writer, fset *token.FileSet, info types.Info, expr *ast.IfStmt, falseExpr ...ast.Expr) error {

	s := brenda.NewSolver(fset, info.Uses, info.Defs, expr.Cond, falseExpr...)
	err := s.SolveTrue()
	if err != nil {
		return err
	}

	fmt.Fprintln(writer, "if", printNode(fset, expr.Cond), "{")

	printMatches(writer, fset, s)

	switch e := expr.Else.(type) {
	case nil:

		fmt.Fprintln(writer, "}")
	case *ast.BlockStmt:

		s := brenda.NewSolver(fset, info.Uses, info.Defs, expr.Cond, falseExpr...)
		s.SolveFalse()
		fmt.Fprintln(writer, "} else {")
		printMatches(writer, fset, s)
		fmt.Fprintln(writer, "}")
	case *ast.IfStmt:

		fmt.Fprint(writer, "} else ")
		falseExpr = append(falseExpr, expr.Cond)
		printIf(writer, fset, info, e, falseExpr...)
	}
	return nil
}

func printMatches(writer io.Writer, fset *token.FileSet, s *brenda.Solver) {
	if s.Impossible {
		fmt.Fprintln(writer, "\t// IMPOSSIBLE")
		return
	}
	var lines []string
	for ex, m := range s.Components {
		switch {
		case m.Match:
			lines = append(lines, fmt.Sprint("\t// ", printNode(fset, ex), " TRUE"))
		case m.Inverse:
			lines = append(lines, fmt.Sprint("\t// ", printNode(fset, ex), " FALSE"))
		default:
			lines = append(lines, fmt.Sprint("\t// ", printNode(fset, ex), " UNKNOWN"))
		}
	}
	sort.Strings(lines)
	fmt.Fprintln(writer, strings.Join(lines, "\n"))
}

func printNode(fset *token.FileSet, node ast.Node) string {
	buf := &bytes.Buffer{}
	err := format.Node(buf, fset, node)
	if err != nil {
		return err.Error()
	}
	return buf.String()
}
Output:
if a == nil && (b && d > 0) || c {
	// a == nil UNKNOWN
	// b UNKNOWN
	// c UNKNOWN
	// d > 0 UNKNOWN
} else if d <= 0 || c {
	// a == nil UNKNOWN
	// b UNKNOWN
	// c FALSE
	// d <= 0 TRUE
} else if b {
	// a == nil FALSE
	// b TRUE
	// c FALSE
	// d > 0 TRUE
}
Example (Or)
package main

import (
	"bytes"
	"go/ast"
	"go/format"
	"go/importer"
	"go/parser"
	"go/token"
	"go/types"
	"io"

	"github.com/dave/brenda"

	"fmt"

	"sort"
	"strings"

	"github.com/pkg/errors"

	"os"
)

func main() {
	printExample(`
		var a, b bool
		if a || b {} else {}
	`)
}

func printExample(src string) {
	err := printOutput(os.Stdout, src)
	if err != nil {
		fmt.Printf("%+v", err)
	}
}

func printOutput(writer io.Writer, src string) error {
	fpath := "/foo.go"
	ppath := "a.b/c"
	src = fmt.Sprintf("package a\n\nfunc a() {\n%s\n}", src)

	fset := token.NewFileSet()
	f, err := parser.ParseFile(fset, fpath, src, parser.ParseComments)
	if err != nil {
		return errors.Wrap(err, "Error parsing file")
	}

	info := types.Info{
		Uses: make(map[*ast.Ident]types.Object),
		Defs: make(map[*ast.Ident]types.Object),
	}
	conf := types.Config{
		Importer: importer.Default(),
	}
	if _, err = conf.Check(ppath, fset, []*ast.File{f}, &info); err != nil {
		return errors.Wrap(err, "Error checking conf")
	}

	var ifs *ast.IfStmt
	ast.Inspect(f, func(node ast.Node) bool {
		if ifs != nil {

			return false
		}
		if n, ok := node.(*ast.IfStmt); ok && n != nil {
			ifs = n
			return false
		}
		return true
	})
	if ifs == nil {
		return errors.New("No *ast.IfStmt found")
	}

	err = printIf(writer, fset, info, ifs)
	if err != nil {
		return err
	}

	return nil
}

func printIf(writer io.Writer, fset *token.FileSet, info types.Info, expr *ast.IfStmt, falseExpr ...ast.Expr) error {

	s := brenda.NewSolver(fset, info.Uses, info.Defs, expr.Cond, falseExpr...)
	err := s.SolveTrue()
	if err != nil {
		return err
	}

	fmt.Fprintln(writer, "if", printNode(fset, expr.Cond), "{")

	printMatches(writer, fset, s)

	switch e := expr.Else.(type) {
	case nil:

		fmt.Fprintln(writer, "}")
	case *ast.BlockStmt:

		s := brenda.NewSolver(fset, info.Uses, info.Defs, expr.Cond, falseExpr...)
		s.SolveFalse()
		fmt.Fprintln(writer, "} else {")
		printMatches(writer, fset, s)
		fmt.Fprintln(writer, "}")
	case *ast.IfStmt:

		fmt.Fprint(writer, "} else ")
		falseExpr = append(falseExpr, expr.Cond)
		printIf(writer, fset, info, e, falseExpr...)
	}
	return nil
}

func printMatches(writer io.Writer, fset *token.FileSet, s *brenda.Solver) {
	if s.Impossible {
		fmt.Fprintln(writer, "\t// IMPOSSIBLE")
		return
	}
	var lines []string
	for ex, m := range s.Components {
		switch {
		case m.Match:
			lines = append(lines, fmt.Sprint("\t// ", printNode(fset, ex), " TRUE"))
		case m.Inverse:
			lines = append(lines, fmt.Sprint("\t// ", printNode(fset, ex), " FALSE"))
		default:
			lines = append(lines, fmt.Sprint("\t// ", printNode(fset, ex), " UNKNOWN"))
		}
	}
	sort.Strings(lines)
	fmt.Fprintln(writer, strings.Join(lines, "\n"))
}

func printNode(fset *token.FileSet, node ast.Node) string {
	buf := &bytes.Buffer{}
	err := format.Node(buf, fset, node)
	if err != nil {
		return err.Error()
	}
	return buf.String()
}
Output:
if a || b {
	// a UNKNOWN
	// b UNKNOWN
} else {
	// a FALSE
	// b FALSE
}
Example (Simple)
package main

import (
	"bytes"
	"go/ast"
	"go/format"
	"go/importer"
	"go/parser"
	"go/token"
	"go/types"
	"io"

	"github.com/dave/brenda"

	"fmt"

	"sort"
	"strings"

	"github.com/pkg/errors"

	"os"
)

func main() {
	printExample(`
		var a bool
		if a { }
	`)
}

func printExample(src string) {
	err := printOutput(os.Stdout, src)
	if err != nil {
		fmt.Printf("%+v", err)
	}
}

func printOutput(writer io.Writer, src string) error {
	fpath := "/foo.go"
	ppath := "a.b/c"
	src = fmt.Sprintf("package a\n\nfunc a() {\n%s\n}", src)

	fset := token.NewFileSet()
	f, err := parser.ParseFile(fset, fpath, src, parser.ParseComments)
	if err != nil {
		return errors.Wrap(err, "Error parsing file")
	}

	info := types.Info{
		Uses: make(map[*ast.Ident]types.Object),
		Defs: make(map[*ast.Ident]types.Object),
	}
	conf := types.Config{
		Importer: importer.Default(),
	}
	if _, err = conf.Check(ppath, fset, []*ast.File{f}, &info); err != nil {
		return errors.Wrap(err, "Error checking conf")
	}

	var ifs *ast.IfStmt
	ast.Inspect(f, func(node ast.Node) bool {
		if ifs != nil {

			return false
		}
		if n, ok := node.(*ast.IfStmt); ok && n != nil {
			ifs = n
			return false
		}
		return true
	})
	if ifs == nil {
		return errors.New("No *ast.IfStmt found")
	}

	err = printIf(writer, fset, info, ifs)
	if err != nil {
		return err
	}

	return nil
}

func printIf(writer io.Writer, fset *token.FileSet, info types.Info, expr *ast.IfStmt, falseExpr ...ast.Expr) error {

	s := brenda.NewSolver(fset, info.Uses, info.Defs, expr.Cond, falseExpr...)
	err := s.SolveTrue()
	if err != nil {
		return err
	}

	fmt.Fprintln(writer, "if", printNode(fset, expr.Cond), "{")

	printMatches(writer, fset, s)

	switch e := expr.Else.(type) {
	case nil:

		fmt.Fprintln(writer, "}")
	case *ast.BlockStmt:

		s := brenda.NewSolver(fset, info.Uses, info.Defs, expr.Cond, falseExpr...)
		s.SolveFalse()
		fmt.Fprintln(writer, "} else {")
		printMatches(writer, fset, s)
		fmt.Fprintln(writer, "}")
	case *ast.IfStmt:

		fmt.Fprint(writer, "} else ")
		falseExpr = append(falseExpr, expr.Cond)
		printIf(writer, fset, info, e, falseExpr...)
	}
	return nil
}

func printMatches(writer io.Writer, fset *token.FileSet, s *brenda.Solver) {
	if s.Impossible {
		fmt.Fprintln(writer, "\t// IMPOSSIBLE")
		return
	}
	var lines []string
	for ex, m := range s.Components {
		switch {
		case m.Match:
			lines = append(lines, fmt.Sprint("\t// ", printNode(fset, ex), " TRUE"))
		case m.Inverse:
			lines = append(lines, fmt.Sprint("\t// ", printNode(fset, ex), " FALSE"))
		default:
			lines = append(lines, fmt.Sprint("\t// ", printNode(fset, ex), " UNKNOWN"))
		}
	}
	sort.Strings(lines)
	fmt.Fprintln(writer, strings.Join(lines, "\n"))
}

func printNode(fset *token.FileSet, node ast.Node) string {
	buf := &bytes.Buffer{}
	err := format.Node(buf, fset, node)
	if err != nil {
		return err.Error()
	}
	return buf.String()
}
Output:
if a {
	// a TRUE
}
Example (Unary)
package main

import (
	"bytes"
	"go/ast"
	"go/format"
	"go/importer"
	"go/parser"
	"go/token"
	"go/types"
	"io"

	"github.com/dave/brenda"

	"fmt"

	"sort"
	"strings"

	"github.com/pkg/errors"

	"os"
)

func main() {
	printExample(`
		var a, b bool
		if !(a || b) {} else {}
	`)
}

func printExample(src string) {
	err := printOutput(os.Stdout, src)
	if err != nil {
		fmt.Printf("%+v", err)
	}
}

func printOutput(writer io.Writer, src string) error {
	fpath := "/foo.go"
	ppath := "a.b/c"
	src = fmt.Sprintf("package a\n\nfunc a() {\n%s\n}", src)

	fset := token.NewFileSet()
	f, err := parser.ParseFile(fset, fpath, src, parser.ParseComments)
	if err != nil {
		return errors.Wrap(err, "Error parsing file")
	}

	info := types.Info{
		Uses: make(map[*ast.Ident]types.Object),
		Defs: make(map[*ast.Ident]types.Object),
	}
	conf := types.Config{
		Importer: importer.Default(),
	}
	if _, err = conf.Check(ppath, fset, []*ast.File{f}, &info); err != nil {
		return errors.Wrap(err, "Error checking conf")
	}

	var ifs *ast.IfStmt
	ast.Inspect(f, func(node ast.Node) bool {
		if ifs != nil {

			return false
		}
		if n, ok := node.(*ast.IfStmt); ok && n != nil {
			ifs = n
			return false
		}
		return true
	})
	if ifs == nil {
		return errors.New("No *ast.IfStmt found")
	}

	err = printIf(writer, fset, info, ifs)
	if err != nil {
		return err
	}

	return nil
}

func printIf(writer io.Writer, fset *token.FileSet, info types.Info, expr *ast.IfStmt, falseExpr ...ast.Expr) error {

	s := brenda.NewSolver(fset, info.Uses, info.Defs, expr.Cond, falseExpr...)
	err := s.SolveTrue()
	if err != nil {
		return err
	}

	fmt.Fprintln(writer, "if", printNode(fset, expr.Cond), "{")

	printMatches(writer, fset, s)

	switch e := expr.Else.(type) {
	case nil:

		fmt.Fprintln(writer, "}")
	case *ast.BlockStmt:

		s := brenda.NewSolver(fset, info.Uses, info.Defs, expr.Cond, falseExpr...)
		s.SolveFalse()
		fmt.Fprintln(writer, "} else {")
		printMatches(writer, fset, s)
		fmt.Fprintln(writer, "}")
	case *ast.IfStmt:

		fmt.Fprint(writer, "} else ")
		falseExpr = append(falseExpr, expr.Cond)
		printIf(writer, fset, info, e, falseExpr...)
	}
	return nil
}

func printMatches(writer io.Writer, fset *token.FileSet, s *brenda.Solver) {
	if s.Impossible {
		fmt.Fprintln(writer, "\t// IMPOSSIBLE")
		return
	}
	var lines []string
	for ex, m := range s.Components {
		switch {
		case m.Match:
			lines = append(lines, fmt.Sprint("\t// ", printNode(fset, ex), " TRUE"))
		case m.Inverse:
			lines = append(lines, fmt.Sprint("\t// ", printNode(fset, ex), " FALSE"))
		default:
			lines = append(lines, fmt.Sprint("\t// ", printNode(fset, ex), " UNKNOWN"))
		}
	}
	sort.Strings(lines)
	fmt.Fprintln(writer, strings.Join(lines, "\n"))
}

func printNode(fset *token.FileSet, node ast.Node) string {
	buf := &bytes.Buffer{}
	err := format.Node(buf, fset, node)
	if err != nil {
		return err.Error()
	}
	return buf.String()
}
Output:
if !(a || b) {
	// a FALSE
	// b FALSE
} else {
	// a UNKNOWN
	// b UNKNOWN
}
Example (Unknown)
package main

import (
	"bytes"
	"go/ast"
	"go/format"
	"go/importer"
	"go/parser"
	"go/token"
	"go/types"
	"io"

	"github.com/dave/brenda"

	"fmt"

	"sort"
	"strings"

	"github.com/pkg/errors"

	"os"
)

func main() {
	printExample(`
		var a, b, c bool
		if a && (b || c) {} else if b {}
	`)
}

func printExample(src string) {
	err := printOutput(os.Stdout, src)
	if err != nil {
		fmt.Printf("%+v", err)
	}
}

func printOutput(writer io.Writer, src string) error {
	fpath := "/foo.go"
	ppath := "a.b/c"
	src = fmt.Sprintf("package a\n\nfunc a() {\n%s\n}", src)

	fset := token.NewFileSet()
	f, err := parser.ParseFile(fset, fpath, src, parser.ParseComments)
	if err != nil {
		return errors.Wrap(err, "Error parsing file")
	}

	info := types.Info{
		Uses: make(map[*ast.Ident]types.Object),
		Defs: make(map[*ast.Ident]types.Object),
	}
	conf := types.Config{
		Importer: importer.Default(),
	}
	if _, err = conf.Check(ppath, fset, []*ast.File{f}, &info); err != nil {
		return errors.Wrap(err, "Error checking conf")
	}

	var ifs *ast.IfStmt
	ast.Inspect(f, func(node ast.Node) bool {
		if ifs != nil {

			return false
		}
		if n, ok := node.(*ast.IfStmt); ok && n != nil {
			ifs = n
			return false
		}
		return true
	})
	if ifs == nil {
		return errors.New("No *ast.IfStmt found")
	}

	err = printIf(writer, fset, info, ifs)
	if err != nil {
		return err
	}

	return nil
}

func printIf(writer io.Writer, fset *token.FileSet, info types.Info, expr *ast.IfStmt, falseExpr ...ast.Expr) error {

	s := brenda.NewSolver(fset, info.Uses, info.Defs, expr.Cond, falseExpr...)
	err := s.SolveTrue()
	if err != nil {
		return err
	}

	fmt.Fprintln(writer, "if", printNode(fset, expr.Cond), "{")

	printMatches(writer, fset, s)

	switch e := expr.Else.(type) {
	case nil:

		fmt.Fprintln(writer, "}")
	case *ast.BlockStmt:

		s := brenda.NewSolver(fset, info.Uses, info.Defs, expr.Cond, falseExpr...)
		s.SolveFalse()
		fmt.Fprintln(writer, "} else {")
		printMatches(writer, fset, s)
		fmt.Fprintln(writer, "}")
	case *ast.IfStmt:

		fmt.Fprint(writer, "} else ")
		falseExpr = append(falseExpr, expr.Cond)
		printIf(writer, fset, info, e, falseExpr...)
	}
	return nil
}

func printMatches(writer io.Writer, fset *token.FileSet, s *brenda.Solver) {
	if s.Impossible {
		fmt.Fprintln(writer, "\t// IMPOSSIBLE")
		return
	}
	var lines []string
	for ex, m := range s.Components {
		switch {
		case m.Match:
			lines = append(lines, fmt.Sprint("\t// ", printNode(fset, ex), " TRUE"))
		case m.Inverse:
			lines = append(lines, fmt.Sprint("\t// ", printNode(fset, ex), " FALSE"))
		default:
			lines = append(lines, fmt.Sprint("\t// ", printNode(fset, ex), " UNKNOWN"))
		}
	}
	sort.Strings(lines)
	fmt.Fprintln(writer, strings.Join(lines, "\n"))
}

func printNode(fset *token.FileSet, node ast.Node) string {
	buf := &bytes.Buffer{}
	err := format.Node(buf, fset, node)
	if err != nil {
		return err.Error()
	}
	return buf.String()
}
Output:
if a && (b || c) {
	// a TRUE
	// b UNKNOWN
	// c UNKNOWN
} else if b {
	// a FALSE
	// b TRUE
	// c UNKNOWN
}
Example (Usage)
package main

import (
	"bytes"
	"go/ast"
	"go/format"
	"go/importer"
	"go/parser"
	"go/token"
	"go/types"

	"github.com/dave/brenda"

	"fmt"

	"sort"
	"strings"
)

func printNode(fset *token.FileSet, node ast.Node) string {
	buf := &bytes.Buffer{}
	err := format.Node(buf, fset, node)
	if err != nil {
		return err.Error()
	}
	return buf.String()
}

func main() {

	// A simple source file
	src := `package foo
	
	func foo(a, b bool) {
		if a { } else if b { } else { }
	}`

	// We parse the AST
	fset := token.NewFileSet()
	f, err := parser.ParseFile(fset, "foo.go", src, 0)
	if err != nil {
		fmt.Println(err)
		return
	}

	// We extract type info
	info := types.Info{
		Uses: make(map[*ast.Ident]types.Object),
		Defs: make(map[*ast.Ident]types.Object),
	}
	conf := types.Config{Importer: importer.Default()}
	if _, err = conf.Check("foo", fset, []*ast.File{f}, &info); err != nil {
		fmt.Println(err)
		return
	}

	// Walk the AST until we find the first *ast.IfStmt
	var ifs *ast.IfStmt
	ast.Inspect(f, func(node ast.Node) bool {
		if ifs != nil {
			return false
		}
		if n, ok := node.(*ast.IfStmt); ok && n != nil {
			ifs = n
			return false
		}
		return true
	})
	if ifs == nil {
		fmt.Println("No *ast.IfStmt found")
		return
	}

	var printIf func(*ast.IfStmt, ...ast.Expr) error
	var sprintResults func(*brenda.Solver) string
	var sprintNode func(ast.Node) string

	// This is called recursively for the if and all else-if statements. falseExpr
	// is a slice of all the conditions that came before an else-if statement,
	// which must all be false for the else-if to be reached.
	printIf = func(ifStmt *ast.IfStmt, falseExpr ...ast.Expr) error {

		s := brenda.NewSolver(fset, info.Uses, info.Defs, ifStmt.Cond, falseExpr...)
		err := s.SolveTrue()
		if err != nil {
			return err
		}

		fmt.Printf("if %s {\n%s\n}", sprintNode(ifStmt.Cond), sprintResults(s))

		switch e := ifStmt.Else.(type) {
		case *ast.BlockStmt:

			// Else block
			s := brenda.NewSolver(fset, info.Uses, info.Defs, ifStmt.Cond, falseExpr...)
			s.SolveFalse()

			fmt.Printf(" else {\n%s\n}", sprintResults(s))

		case *ast.IfStmt:

			// Else if statement
			fmt.Print(" else ")

			// Add the condition from the current if statement to the list of
			// false expressions for the else-if solver
			falseExpr = append(falseExpr, ifStmt.Cond)
			printIf(e, falseExpr...)

		}
		return nil
	}

	// Helper function to print results
	sprintResults = func(s *brenda.Solver) string {
		if s.Impossible {
			// If the expression is impossible
			return "\t// IMPOSSIBLE"
		}

		// The results must be sorted to ensure repeatable output
		var lines []string
		for expr, result := range s.Components {
			switch {
			case result.Match:
				lines = append(lines, fmt.Sprint("\t// ", printNode(fset, expr), " TRUE"))
			case result.Inverse:
				lines = append(lines, fmt.Sprint("\t// ", printNode(fset, expr), " FALSE"))
			default:
				lines = append(lines, fmt.Sprint("\t// ", printNode(fset, expr), " UNKNOWN"))
			}
		}
		sort.Strings(lines)
		return strings.Join(lines, "\n")
	}

	// Helper function to print AST nodes
	sprintNode = func(n ast.Node) string {
		buf := &bytes.Buffer{}
		err := format.Node(buf, fset, n)
		if err != nil {
			return err.Error()
		}
		return buf.String()
	}

	if err := printIf(ifs); err != nil {
		fmt.Println(err)
		return
	}

}
Output:
if a {
	// a TRUE
} else if b {
	// a FALSE
	// b TRUE
} else {
	// a FALSE
	// b FALSE
}

func (*Solver) SolveFalse

func (s *Solver) SolveFalse() error

SolveFalse solves the expression as false - e.g. for the else block of an if statement

func (*Solver) SolveTrue

func (s *Solver) SolveTrue() error

SolveTrue solves the expression as true - e.g. for the main block of an if statement

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL