From ac39606386949fbe2cd4ae1e0f30d7bb0e1143e7 Mon Sep 17 00:00:00 2001 From: James Shubin Date: Thu, 4 Aug 2022 14:49:24 -0400 Subject: [PATCH] lang: Misc changes from an old feature branch --- lang/gapi/gapi.go | 13 +++++---- lang/interfaces/func.go | 3 ++ lang/interfaces/structs.go | 47 +++++++++++++++++++++++++++++--- lang/lang.go | 6 ++-- lang/unification/simplesolver.go | 5 +++- 5 files changed, 60 insertions(+), 14 deletions(-) diff --git a/lang/gapi/gapi.go b/lang/gapi/gapi.go index 108c527c..610ceba0 100644 --- a/lang/gapi/gapi.go +++ b/lang/gapi/gapi.go @@ -270,7 +270,7 @@ func (obj *GAPI) Cli(cliInfo *gapi.CliInfo) (*gapi.Deploy, error) { logf("interpolating...") // interpolate strings and other expansionable nodes in AST - interpolated, err := xast.Interpolate() + iast, err := xast.Interpolate() if err != nil { return nil, errwrap.Wrapf(err, "could not interpolate AST") } @@ -301,7 +301,7 @@ func (obj *GAPI) Cli(cliInfo *gapi.CliInfo) (*gapi.Deploy, error) { // operation should not depend on any initial scope values, since those // would all be runtime changes, and we do not support dynamic imports, // however, we need to since we're doing type unification to err early! - if err := interpolated.SetScope(scope); err != nil { // empty initial scope! + if err := iast.SetScope(scope); err != nil { // empty initial scope! return nil, errwrap.Wrapf(err, "could not set scope") } @@ -313,7 +313,7 @@ func (obj *GAPI) Cli(cliInfo *gapi.CliInfo) (*gapi.Deploy, error) { } logf("running type unification...") unifier := &unification.Unifier{ - AST: interpolated, + AST: iast, Solver: unification.SimpleInvariantSolverLogger(unificationLogf), Debug: debug, Logf: unificationLogf, @@ -323,7 +323,7 @@ func (obj *GAPI) Cli(cliInfo *gapi.CliInfo) (*gapi.Deploy, error) { } // get the list of needed files (this is available after SetScope) - fileList, err := ast.CollectFiles(interpolated) + fileList, err := ast.CollectFiles(iast) if err != nil { return nil, errwrap.Wrapf(err, "could not collect files") } @@ -743,7 +743,7 @@ func (obj *GAPI) Get(getInfo *gapi.GetInfo) error { logf("interpolating...") // interpolate strings and other expansionable nodes in AST - interpolated, err := ast.Interpolate() + iast, err := ast.Interpolate() if err != nil { return errwrap.Wrapf(err, "could not interpolate AST") } @@ -754,7 +754,8 @@ func (obj *GAPI) Get(getInfo *gapi.GetInfo) error { // don't think we need to pass in an initial scope because the download // operation shouldn't depend on any initial scope values, since those // would all be runtime changes, and we do not support dynamic imports! - if err := interpolated.SetScope(nil); err != nil { // empty initial scope! + // XXX Add non-empty scope? + if err := iast.SetScope(nil); err != nil { // empty initial scope! return errwrap.Wrapf(err, "could not set scope") } diff --git a/lang/interfaces/func.go b/lang/interfaces/func.go index 92e39483..c8690f2d 100644 --- a/lang/interfaces/func.go +++ b/lang/interfaces/func.go @@ -22,6 +22,9 @@ import ( "github.com/purpleidea/mgmt/lang/types" ) +// FuncSig is the simple signature that is used throughout our implementations. +type FuncSig = func([]types.Value) (types.Value, error) + // Info is a static representation of some information about the function. It is // used for static analysis and type checking. If you break this contract, you // might cause a panic. diff --git a/lang/interfaces/structs.go b/lang/interfaces/structs.go index 293a6b2f..690afb53 100644 --- a/lang/interfaces/structs.go +++ b/lang/interfaces/structs.go @@ -28,6 +28,8 @@ import ( // ExprAny is a placeholder expression that is used for type unification hacks. type ExprAny struct { typ *types.Type + + V types.Value // stored value (set with SetValue) } // String returns a short representation of this expression. @@ -51,6 +53,7 @@ func (obj *ExprAny) Init(*Data) error { return nil } func (obj *ExprAny) Interpolate() (Expr, error) { return &ExprAny{ typ: obj.typ, + V: obj.V, }, nil } @@ -85,15 +88,30 @@ func (obj *ExprAny) SetType(typ *types.Type) error { if obj.typ != nil { return obj.typ.Cmp(typ) // if not set, ensure it doesn't change } + if obj.V != nil { + // if there's a value already, ensure the types are the same... + if err := obj.V.Type().Cmp(typ); err != nil { + return err + } + } obj.typ = typ // set return nil } // Type returns the type of this expression. func (obj *ExprAny) Type() (*types.Type, error) { - if obj.typ == nil { + if obj.typ == nil && obj.V == nil { return nil, ErrTypeCurrentlyUnknown } + if obj.typ != nil && obj.V != nil { + if err := obj.V.Type().Cmp(obj.typ); err != nil { + return nil, err + } + return obj.typ, nil + } + if obj.V != nil { + return obj.V.Type(), nil + } return obj.typ, nil } @@ -106,6 +124,8 @@ func (obj *ExprAny) Unify() ([]Invariant, error) { Expr: obj, }, } + // TODO: should we return an EqualsInvariant with obj.typ ? + // TODO: should we return a ValueInvariant with obj.V ? return invariants, nil } @@ -127,20 +147,39 @@ func (obj *ExprAny) Graph() (*pgraph.Graph, error) { // Func returns the reactive stream of values that this expression produces. func (obj *ExprAny) Func() (Func, error) { - return nil, fmt.Errorf("programming error") // this should not be called + // // XXX: this could be a list too, so improve this code or improve the subgraph code... + // return &structs.ConstFunc{ + // Value: obj.V, + // } + + return nil, fmt.Errorf("programming error using ExprAny") // this should not be called } // SetValue here is a no-op, because algorithmically when this is called from // the func engine, the child elements (the list elements) will have had this // done to them first, and as such when we try and retrieve the set value from // this expression by calling `Value`, it will build it from scratch! + +// SetValue here is used to store a value for this expression node. This value +// is cached and can be retrieved by calling Value. func (obj *ExprAny) SetValue(value types.Value) error { - return fmt.Errorf("programming error") // this should not be called + typ := value.Type() + if obj.typ != nil { + if err := obj.typ.Cmp(typ); err != nil { + return err + } + } + obj.typ = typ + obj.V = value + return nil } // Value returns the value of this expression in our type system. This will // usually only be valid once the engine has run and values have been produced. // This might get called speculatively (early) during unification to learn more. func (obj *ExprAny) Value() (types.Value, error) { - return nil, fmt.Errorf("programming error") // this should not be called + if obj.V == nil { + return nil, fmt.Errorf("value is not set") + } + return obj.V, nil } diff --git a/lang/lang.go b/lang/lang.go index 39a9e29a..e9bd31f2 100644 --- a/lang/lang.go +++ b/lang/lang.go @@ -164,11 +164,11 @@ func (obj *Lang) Init() error { obj.Logf("interpolating...") // interpolate strings and other expansionable nodes in AST - interpolated, err := xast.Interpolate() + iast, err := xast.Interpolate() if err != nil { return errwrap.Wrapf(err, "could not interpolate AST") } - obj.ast = interpolated + obj.ast = iast variables := map[string]interfaces.Expr{ "purpleidea": &ast.ExprStr{V: "hello world!"}, // james says hi @@ -301,7 +301,7 @@ func (obj *Lang) Init() error { select { case obj.streamChan <- err: // send if err != nil { - obj.Logf("Stream error: %+v", err) + obj.Logf("stream error: %+v", err) return } diff --git a/lang/unification/simplesolver.go b/lang/unification/simplesolver.go index 9a291b84..9660215f 100644 --- a/lang/unification/simplesolver.go +++ b/lang/unification/simplesolver.go @@ -888,7 +888,10 @@ Loop: logf("%s: unsolved equality: %+v", Name, x) } for x := range unsolved { - logf("%s: unsolved expected: %+v", Name, x) + logf("%s: unsolved expected: (%p) %+v", Name, x, x) + } + for expr, typ := range solved { + logf("%s: solved: (%p) => %+v", Name, expr, typ) } return nil, ErrAmbiguous }