diff --git a/cli/util/args.go b/cli/util/args.go index 5595a5ca..15a0c408 100644 --- a/cli/util/args.go +++ b/cli/util/args.go @@ -84,8 +84,9 @@ type LangArgs struct { OnlyDownload bool `arg:"--only-download" help:"stop after downloading any missing imports"` Update bool `arg:"--update" help:"update all dependencies to the latest versions"` - OnlyUnify bool `arg:"--only-unify" help:"stop after type unification"` - SkipUnify bool `arg:"--skip-unify" help:"skip type unification"` + OnlyUnify bool `arg:"--only-unify" help:"stop after type unification"` + SkipUnify bool `arg:"--skip-unify" help:"skip type unification"` + UnifySolver *string `arg:"--unify-name" help:"pick a specific unification solver"` Depth int `arg:"--depth" default:"-1" help:"max recursion depth limit (-1 is unlimited)"` diff --git a/lang/gapi/gapi.go b/lang/gapi/gapi.go index 208555bf..589d84fe 100644 --- a/lang/gapi/gapi.go +++ b/lang/gapi/gapi.go @@ -70,6 +70,9 @@ func init() { type GAPI struct { InputURI string // input URI of code file system to run + // Data is some additional data for the lang struct. + Data *lang.Data + lang *lang.Lang // lang struct wgRun *sync.WaitGroup ctx context.Context @@ -261,6 +264,11 @@ func (obj *GAPI) Cli(info *gapi.Info) (*gapi.Deploy, error) { return nil, nil // success! } + unificationStrategy := make(map[string]string) + if name := args.UnifySolver; name != nil && *name != "" { + unificationStrategy[unification.StrategyNameKey] = *name + } + if !args.SkipUnify { // apply type unification unificationLogf := func(format string, v ...interface{}) { @@ -275,10 +283,11 @@ func (obj *GAPI) Cli(info *gapi.Info) (*gapi.Deploy, error) { return nil, errwrap.Wrapf(err, "could not get default solver") } unifier := &unification.Unifier{ - AST: iast, - Solver: solver, - Debug: debug, - Logf: unificationLogf, + AST: iast, + Solver: solver, + Strategy: unificationStrategy, + Debug: debug, + Logf: unificationLogf, } startTime := time.Now() unifyErr := unifier.Unify(context.TODO()) @@ -411,7 +420,10 @@ func (obj *GAPI) Cli(info *gapi.Info) (*gapi.Deploy, error) { Sema: info.Flags.Sema, GAPI: &GAPI{ InputURI: fs.URI(), - // TODO: add properties here... + Data: &lang.Data{ + UnificationStrategy: unificationStrategy, + // TODO: add properties here... + }, }, }, nil } @@ -451,6 +463,7 @@ func (obj *GAPI) LangInit(ctx context.Context) error { Fs: fs, FsURI: obj.InputURI, Input: input, + Data: obj.Data, Hostname: obj.data.Hostname, Local: obj.data.Local, diff --git a/lang/lang.go b/lang/lang.go index 6e03fa04..c9183807 100644 --- a/lang/lang.go +++ b/lang/lang.go @@ -64,6 +64,18 @@ const ( EngineStartupStatsTimeout = 10 ) +// Data is some data that is passed into the Lang struct. It is presented here +// as a single struct with room for multiple fields so that it can be changed or +// extended in the future without having to re-plumb through all the fields it +// contains +type Data struct { + // UnificationStrategy is a hack to tune unification performance until + // we have an overall cleaner unification algorithm in place. + UnificationStrategy map[string]string + + // TODO: Add other fields here if necessary. +} + // Lang is the main language lexer/parser object. type Lang struct { Fs engine.Fs // connected fs where input dir or metadata exists @@ -79,6 +91,9 @@ type Lang struct { // run the raw string as mcl code. Input string + // Data is some additional data for the lang struct. + Data *Data + Hostname string Local *local.API World engine.World @@ -101,6 +116,12 @@ type Lang struct { // watching them, *before* we pull their values, that way we'll know if they // changed from the values we wanted. func (obj *Lang) Init(ctx context.Context) error { + if obj.Data == nil { + return fmt.Errorf("lang struct was not built properly") + } + if obj.Data.UnificationStrategy == nil { + return fmt.Errorf("lang struct was not built properly") + } if obj.Debug { obj.Logf("input: %s", obj.Input) tree, err := util.FsTree(obj.Fs, "/") // should look like gapi @@ -227,15 +248,20 @@ func (obj *Lang) Init(ctx context.Context) error { } obj.Logf("running type unification...") - solver, err := unification.LookupDefault() - if err != nil { + var solver unification.Solver + if name, exists := obj.Data.UnificationStrategy["solver"]; exists && name != "" { + if solver, err = unification.Lookup(name); err != nil { + return errwrap.Wrapf(err, "could not get solver: %s", name) + } + } else if solver, err = unification.LookupDefault(); err != nil { return errwrap.Wrapf(err, "could not get default solver") } unifier := &unification.Unifier{ - AST: obj.ast, - Solver: solver, - Debug: obj.Debug, - Logf: logf, + AST: obj.ast, + Solver: solver, + Strategy: obj.Data.UnificationStrategy, + Debug: obj.Debug, + Logf: logf, } timing = time.Now() // NOTE: This is the "real" Unify that runs. (This is not for deploy.) diff --git a/lang/lang_test.go b/lang/lang_test.go index f2ff9569..32b77db9 100644 --- a/lang/lang_test.go +++ b/lang/lang_test.go @@ -137,7 +137,10 @@ func runInterpret(t *testing.T, code string) (_ *pgraph.Graph, reterr error) { lang := &Lang{ Fs: fs, Input: "/" + interfaces.MetadataFilename, // start path in fs - Debug: testing.Verbose(), // set via the -test.v flag to `go test` + Data: &Data{ + UnificationStrategy: make(map[string]string), // empty + }, + Debug: testing.Verbose(), // set via the -test.v flag to `go test` Logf: logf, } if err := lang.Init(ctx); err != nil { diff --git a/lang/unification/interfaces.go b/lang/unification/interfaces.go index 8e31deb5..0b39af9b 100644 --- a/lang/unification/interfaces.go +++ b/lang/unification/interfaces.go @@ -42,11 +42,18 @@ const ( // ErrAmbiguous means we couldn't find a solution, but we weren't // inconsistent. ErrAmbiguous = interfaces.Error("can't unify, no equalities were consumed, we're ambiguous") + + // StrategyNameKey is the string key used when choosing a solver name. + StrategyNameKey = "name" ) // Init contains some handles that are used to initialize every solver. Each // individual solver can choose to omit using some of the fields. type Init struct { + // Strategy is a hack to tune unification performance until we have an + // overall cleaner unification algorithm in place. + Strategy map[string]string + Debug bool Logf func(format string, v ...interface{}) } diff --git a/lang/unification/simplesolver/simplesolver.go b/lang/unification/simplesolver/simplesolver.go index 92c98a4c..60e71aa8 100644 --- a/lang/unification/simplesolver/simplesolver.go +++ b/lang/unification/simplesolver/simplesolver.go @@ -75,6 +75,8 @@ type SimpleInvariantSolver struct { // Init contains some handles that are used to initialize the solver. func (obj *SimpleInvariantSolver) Init(init *unification.Init) error { + obj.Strategy = init.Strategy + obj.Debug = init.Debug obj.Logf = init.Logf diff --git a/lang/unification/unification.go b/lang/unification/unification.go index f3888cf5..b7f8b1a7 100644 --- a/lang/unification/unification.go +++ b/lang/unification/unification.go @@ -48,6 +48,10 @@ type Unifier struct { // Solver is the solver algorithm implementation to use. Solver Solver + // Strategy is a hack to tune unification performance until we have an + // overall cleaner unification algorithm in place. + Strategy map[string]string + Debug bool Logf func(format string, v ...interface{}) } @@ -76,8 +80,9 @@ func (obj *Unifier) Unify(ctx context.Context) error { } init := &Init{ - Logf: obj.Logf, - Debug: obj.Debug, + Strategy: obj.Strategy, + Logf: obj.Logf, + Debug: obj.Debug, } if err := obj.Solver.Init(init); err != nil { return err