func (pipeline *Pipeline) EquivalentTo(other Callable,
	myCallables, otherCallables *Callables) bool {
	if pipeline == nil {
		return other == nil
	} else if other == nil {
		return false
	}
	if op, ok := other.(*Pipeline); !ok {
		return false
	} else if !pipeline.InParams.Equals(op.InParams) {
		util.PrintInfo("compare",
			"Pipeline %s in params unequal.",
			pipeline.Id)
		return false
	} else if !pipeline.OutParams.Equals(op.OutParams, true) {
		util.PrintInfo("compare",
			"Pipeline %s out params unequal.",
			pipeline.Id)
		return false
	} else if len(pipeline.Calls) != len(op.Calls) {
		util.PrintInfo("compare",
			"Pipeline %s call count changed.",
			pipeline.Id)
		return false
	} else if pipeline.Ret == nil || op.Ret == nil ||
		!pipeline.Ret.Bindings.Equals(op.Ret.Bindings) {
		util.PrintInfo("compare",
			"Pipeline %s return bindings unequal.",
			pipeline.Id)
		return false
	} else {
		oCalls := make(map[string]*CallStm, len(op.Calls))
		for _, call := range op.Calls {
			oCalls[call.Id] = call
		}
		for _, call := range pipeline.Calls {
			if !call.EquivalentTo(oCalls[call.Id], myCallables, otherCallables) {
				return false
			}
		}
		return true
	}
}
Two pipelines are semantically equivalent if their input and output argument
names are the same and all of their calls are semantically equivalent and
their return bindings are the same.  Changes to VDR retention are ignored.
package syntax
import (
	"errors"
	"fmt"
	"math"
	"github.com/martian-lang/martian/martian/util"
)
func (ast *Ast) EquivalentCall(other *Ast) bool {
	if ast.Callables == nil || ast.Callables.Table == nil ||
		other.Callables == nil || other.Callables.Table == nil {
		return false
	}
	return ast.Call.EquivalentTo(other.Call, ast.Callables, other.Callables)
}
func (call *CallStm) EquivalentTo(other *CallStm,
	myCallables, otherCallables *Callables) bool {
	if call == nil {
		return other == nil
	} else if other == nil {
		return false
	}
	if call.Id != other.Id {
		util.PrintInfo("compare",
			"Call name %s does not match %s",
			call.Id, other.Id)
		return false
	}
	if !call.Bindings.Equals(other.Bindings) {
		util.PrintInfo("compare",
			"Call bindings for %s do not match.",
			call.Id)
		return false
	}
	if !call.Modifiers.EquivalentTo(other.Modifiers) {
		util.PrintInfo("compare",
			"Call modifiers do not match.")
		return false
	}
	if callable := myCallables.Table[call.DecId]; callable == nil {
		return otherCallables.Table[other.DecId] == nil
	} else if oc := otherCallables.Table[other.DecId]; oc == nil {
		util.PrintInfo("compare",
			"Callable %s not found",
			other.Id)
		return false
	} else {
		return callable.EquivalentTo(oc, myCallables, otherCallables)
	}
}
func (bindings *BindStms) Equals(other *BindStms) bool {
	if bindings == nil || len(bindings.List) == 0 {
		return other == nil || len(other.List) == 0
	} else if other == nil || other.Table == nil ||
		len(other.List) != len(bindings.List) {
		return false
	}
	for _, b := range bindings.List {
		if b.Id == "*" {
			continue
		}
		if ob := other.Table[b.Id]; ob == nil {
			util.PrintInfo("compare",
				"Binding ID %s not found",
				b.Id)
			return false
		} else if !b.Equals(ob) {
			return false
		}
	}
	return true
}
func (mods *Modifiers) EquivalentTo(other *Modifiers) bool {
	if mods == nil {
		if other == nil {
			return true
		} else {
			return other.EquivalentTo(mods)
		}
	} else if other == nil {
		if mods.Local || mods.Preflight {
			return false
		} else {
			return mods.Bindings == nil || mods.Bindings.Table == nil ||
				mods.Bindings.Table[disabled] == nil
		}
	} else if mods.Local != other.Local || mods.Preflight != other.Preflight {
		return false
	} else if mods.Bindings != nil && mods.Bindings.Table != nil {
		if b := mods.Bindings.Table[disabled]; b != nil {
			if other.Bindings == nil || other.Bindings.Table == nil {
				return false
			} else if ob := mods.Bindings.Table[disabled]; ob == nil {
				return false
			} else {
				return b.Equals(ob)
			}
		}
	}
	if other.Bindings != nil && other.Bindings.Table != nil {
		return other.Bindings.Table[disabled] == nil
	} else {
		return true
	}
}
func (params *InParams) Equals(other *InParams) bool {
	if params == nil || len(params.List) == 0 {
		return other == nil || len(other.List) == 0
	} else if other == nil || other.Table == nil ||
		len(other.Table) != len(params.List) {
		util.PrintInfo("compare",
			"Argument length mismatch.")
		return false
	}
	for _, arg := range params.List {
		if oa := other.Table[arg.GetId()]; oa == nil {
			util.PrintInfo("compare",
				"Argument %s not found.",
				arg.GetId())
			return false
		} else if arg.GetArrayDim() != oa.GetArrayDim() {
			return false
		} else if arg.IsFile() != oa.IsFile() {
			return false
		} else if arg.IsFile() != KindIsFile && arg.GetTname() != oa.GetTname() {
			return false
		}
	}
	return true
}
func (params *OutParams) Equals(other *OutParams, checkOutNames bool) bool {
	if params == nil || len(params.List) == 0 {
		return other == nil || len(other.List) == 0
	} else if other == nil || other.Table == nil ||
		len(other.Table) != len(params.List) {
		util.PrintInfo("compare",
			"Argument length mismatch.")
		return false
	}
	for _, arg := range params.List {
		if oa := other.Table[arg.GetId()]; oa == nil {
			util.PrintInfo("compare",
				"Argument %s not found.",
				arg.GetId())
			return false
		} else if arg.GetArrayDim() != oa.GetArrayDim() {
			return false
		} else if arg.IsFile() != oa.IsFile() {
			return false
		} else if fk := arg.IsFile(); fk != KindIsFile &&
			arg.GetTname() != oa.GetTname() {
			return false
		} else if (fk == KindIsFile || fk == KindIsDirectory) &&
			checkOutNames && arg.GetOutName() != oa.GetOutName() {
			return false
		}
	}
	return true
}