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