Skip to content

Commit 2b820da

Browse files
committed
lang: ast: structs, funcs: structs: Exprif without a channel
This adds an improved "expr if" which only adds the active branch to the graph and removes the "secret" channel.
1 parent 86c6ee8 commit 2b820da

File tree

7 files changed

+306
-236
lines changed

7 files changed

+306
-236
lines changed

lang/ast/structs.go

Lines changed: 52 additions & 36 deletions
Original file line numberDiff line numberDiff line change
@@ -12716,59 +12716,75 @@ func (obj *ExprIf) Check(typ *types.Type) ([]*interfaces.UnificationInvariant, e
1271612716
return interfaces.GenericCheck(obj, typ)
1271712717
}
1271812718

12719-
// Func returns a function which returns the correct branch based on the ever
12720-
// changing conditional boolean input.
12721-
func (obj *ExprIf) Func() (interfaces.Func, error) {
12719+
// Graph returns the reactive function graph which is expressed by this node. It
12720+
// includes the condition produced by this node, and the appropriate edges of
12721+
// that. The then or else side of the graph is added at runtime based on the
12722+
// value of the condition.
12723+
// TODO: If we know the condition is static, generate only that side statically.
12724+
func (obj *ExprIf) Graph(env *interfaces.Env) (*pgraph.Graph, interfaces.Func, error) {
12725+
graph, err := pgraph.NewGraph("if")
12726+
if err != nil {
12727+
return nil, nil, err
12728+
}
12729+
1272212730
typ, err := obj.Type()
1272312731
if err != nil {
12724-
return nil, err
12732+
return nil, nil, err
1272512733
}
1272612734

12727-
return &structs.IfFunc{
12728-
Textarea: obj.Textarea,
12735+
// XXX: can we speculate if it's static?
1272912736

12730-
Type: typ, // this is the output type of the expression
12731-
}, nil
12732-
}
12737+
g, f, err := obj.Condition.Graph(env)
12738+
if err != nil {
12739+
return nil, nil, err
12740+
}
12741+
graph.AddGraph(g)
1273312742

12734-
// Graph returns the reactive function graph which is expressed by this node. It
12735-
// includes any vertices produced by this node, and the appropriate edges to any
12736-
// vertices that are produced by its children. Nodes which fulfill the Expr
12737-
// interface directly produce vertices (and possible children) where as nodes
12738-
// that fulfill the Stmt interface do not produces vertices, where as their
12739-
// children might. This particular if expression doesn't do anything clever here
12740-
// other than adding in both branches of the graph. Since we're functional, this
12741-
// shouldn't have any ill effects.
12742-
// XXX: is this completely true if we're running technically impure, but safe
12743-
// built-in functions on both branches? Can we turn off half of this?
12744-
func (obj *ExprIf) Graph(env *interfaces.Env) (*pgraph.Graph, interfaces.Func, error) {
12745-
graph, err := pgraph.NewGraph("if")
12743+
thenGraph, thenFunc, err := obj.ThenBranch.Graph(env)
1274612744
if err != nil {
1274712745
return nil, nil, err
1274812746
}
12749-
function, err := obj.Func()
12747+
elseGraph, elseFunc, err := obj.ElseBranch.Graph(env)
1275012748
if err != nil {
1275112749
return nil, nil, err
1275212750
}
1275312751

12754-
exprs := map[string]interfaces.Expr{
12755-
"c": obj.Condition,
12756-
"a": obj.ThenBranch,
12757-
"b": obj.ElseBranch,
12752+
edgeName := structs.ExprIfFuncArgNameCondition
12753+
edgeNameDummy := structs.OutputFuncDummyArgName
12754+
12755+
exprIfSubgraphOutput := &structs.OutputFunc{ // the new graph shape thing!
12756+
Textarea: obj.Textarea,
12757+
Name: "exprIfSubgraphOutput",
12758+
Type: obj.typ,
12759+
EdgeName: structs.OutputFuncArgName,
1275812760
}
12759-
for _, argName := range []string{"c", "a", "b"} { // deterministic order
12760-
x := exprs[argName]
12761-
g, f, err := x.Graph(env)
12762-
if err != nil {
12763-
return nil, nil, err
12764-
}
12765-
graph.AddGraph(g)
12761+
graph.AddVertex(exprIfSubgraphOutput)
1276612762

12767-
edge := &interfaces.FuncEdge{Args: []string{argName}}
12768-
graph.AddEdge(f, function, edge) // branch -> if
12763+
function := &structs.ExprIfFunc{
12764+
Textarea: obj.Textarea,
12765+
12766+
Type: typ, // this is the output type of the expression
12767+
12768+
EdgeName: edgeName,
12769+
12770+
ThenGraph: thenGraph,
12771+
ElseGraph: elseGraph,
12772+
12773+
ThenFunc: thenFunc,
12774+
ElseFunc: elseFunc,
12775+
12776+
OutputVertex: exprIfSubgraphOutput,
1276912777
}
12778+
graph.AddVertex(function)
1277012779

12771-
return graph, function, nil
12780+
edge := &interfaces.FuncEdge{Args: []string{edgeName}}
12781+
graph.AddEdge(f, function, edge) // condition -> exprif
12782+
12783+
graph.AddEdge(function, exprIfSubgraphOutput, &interfaces.FuncEdge{
12784+
Args: []string{edgeNameDummy},
12785+
})
12786+
12787+
return graph, exprIfSubgraphOutput, nil
1277212788
}
1277312789

1277412790
// SetValue here is a no-op, because algorithmically when this is called from

lang/funcs/structs/exprif.go

Lines changed: 231 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,231 @@
1+
// Mgmt
2+
// Copyright (C) James Shubin and the project contributors
3+
// Written by James Shubin <james@shubin.ca> and the project contributors
4+
//
5+
// This program is free software: you can redistribute it and/or modify
6+
// it under the terms of the GNU General Public License as published by
7+
// the Free Software Foundation, either version 3 of the License, or
8+
// (at your option) any later version.
9+
//
10+
// This program is distributed in the hope that it will be useful,
11+
// but WITHOUT ANY WARRANTY; without even the implied warranty of
12+
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13+
// GNU General Public License for more details.
14+
//
15+
// You should have received a copy of the GNU General Public License
16+
// along with this program. If not, see <https://www.gnu.org/licenses/>.
17+
//
18+
// Additional permission under GNU GPL version 3 section 7
19+
//
20+
// If you modify this program, or any covered work, by linking or combining it
21+
// with embedded mcl code and modules (and that the embedded mcl code and
22+
// modules which link with this program, contain a copy of their source code in
23+
// the authoritative form) containing parts covered by the terms of any other
24+
// license, the licensors of this program grant you additional permission to
25+
// convey the resulting work. Furthermore, the licensors of this program grant
26+
// the original author, James Shubin, additional permission to update this
27+
// additional permission if he deems it necessary to achieve the goals of this
28+
// additional permission.
29+
30+
package structs
31+
32+
import (
33+
"context"
34+
"fmt"
35+
"sync"
36+
37+
"github.com/purpleidea/mgmt/lang/interfaces"
38+
"github.com/purpleidea/mgmt/lang/types"
39+
"github.com/purpleidea/mgmt/pgraph"
40+
"github.com/purpleidea/mgmt/util/errwrap"
41+
)
42+
43+
const (
44+
// ExprIfFuncName is the unique name identifier for this function.
45+
ExprIfFuncName = "exprif"
46+
47+
// ExprIfFuncArgNameCondition is the name for the edge which connects
48+
// the input condition to ExprIfFunc.
49+
ExprIfFuncArgNameCondition = "condition"
50+
)
51+
52+
// ExprIfFunc is a function that passes through the value of the correct branch
53+
// based on the conditional value it gets.
54+
type ExprIfFunc struct {
55+
interfaces.Textarea
56+
57+
Type *types.Type // this is the type of the if expression output we hold
58+
59+
EdgeName string // name of the edge used
60+
61+
ThenGraph *pgraph.Graph
62+
ElseGraph *pgraph.Graph
63+
64+
ThenFunc interfaces.Func
65+
ElseFunc interfaces.Func
66+
67+
OutputVertex interfaces.Func
68+
69+
init *interfaces.Init
70+
last *bool // last value received to use for diff
71+
}
72+
73+
// String returns a simple name for this function. This is needed so this struct
74+
// can satisfy the pgraph.Vertex interface.
75+
func (obj *ExprIfFunc) String() string {
76+
return ExprIfFuncName
77+
}
78+
79+
// Validate tells us if the input struct takes a valid form.
80+
func (obj *ExprIfFunc) Validate() error {
81+
if obj.Type == nil {
82+
return fmt.Errorf("must specify a type")
83+
}
84+
85+
if obj.EdgeName == "" {
86+
return fmt.Errorf("must specify an edge name")
87+
}
88+
89+
if obj.ThenFunc == nil {
90+
return fmt.Errorf("must specify a then func")
91+
}
92+
if obj.ElseFunc == nil {
93+
return fmt.Errorf("must specify an else func")
94+
}
95+
96+
t1 := obj.ThenFunc.Info().Sig.Out
97+
t2 := obj.ElseFunc.Info().Sig.Out
98+
if err := t1.Cmp(t2); err != nil {
99+
return errwrap.Wrapf(err, "types of exprif branches must match")
100+
}
101+
102+
if obj.OutputVertex == nil {
103+
return fmt.Errorf("the output vertex is missing")
104+
}
105+
106+
return nil
107+
}
108+
109+
// Info returns some static info about itself.
110+
func (obj *ExprIfFunc) Info() *interfaces.Info {
111+
var typ *types.Type
112+
if obj.Type != nil { // don't panic if called speculatively
113+
typ = &types.Type{
114+
Kind: types.KindFunc, // function type
115+
Map: map[string]*types.Type{
116+
ExprIfFuncArgNameCondition: types.TypeBool, // conditional must be a boolean
117+
},
118+
Ord: []string{ExprIfFuncArgNameCondition}, // conditional
119+
Out: obj.Type, // result type must match
120+
}
121+
}
122+
123+
return &interfaces.Info{
124+
Pure: true,
125+
Memo: false, // TODO: ???
126+
Sig: typ,
127+
Err: obj.Validate(),
128+
}
129+
}
130+
131+
// Init runs some startup code for this if expression function.
132+
func (obj *ExprIfFunc) Init(init *interfaces.Init) error {
133+
obj.init = init
134+
return nil
135+
}
136+
137+
// Stream takes an input struct in the format as described in the Func and Graph
138+
// methods of the Expr, and returns the actual expected value as a stream based
139+
// on the changing inputs to that value.
140+
func (obj *ExprIfFunc) Stream(ctx context.Context) error {
141+
// XXX: is there a sync.Once sort of solution that would be more elegant here?
142+
mutex := &sync.Mutex{}
143+
done := false
144+
send := func(ctx context.Context, b bool) error {
145+
mutex.Lock()
146+
defer mutex.Unlock()
147+
if done {
148+
return nil
149+
}
150+
done = true
151+
defer close(obj.init.Output) // the sender closes
152+
153+
if !b {
154+
return nil
155+
}
156+
157+
// send dummy value to the output
158+
select {
159+
case obj.init.Output <- types.NewFloat(): // XXX: dummy value
160+
case <-ctx.Done():
161+
return ctx.Err()
162+
}
163+
164+
return nil
165+
}
166+
defer send(ctx, false) // just close
167+
168+
defer func() {
169+
obj.init.Txn.Reverse()
170+
}()
171+
172+
for {
173+
select {
174+
case input, ok := <-obj.init.Input:
175+
if !ok {
176+
obj.init.Input = nil // block looping back here
177+
if !done {
178+
return fmt.Errorf("input closed without ever sending anything")
179+
}
180+
return nil
181+
}
182+
183+
value, exists := input.Struct()[obj.EdgeName]
184+
if !exists {
185+
return fmt.Errorf("programming error, can't find edge")
186+
}
187+
188+
b := value.Bool()
189+
190+
if obj.last != nil && *obj.last == b {
191+
continue // result didn't change
192+
}
193+
obj.last = &b // store new result
194+
195+
if err := obj.replaceSubGraph(b); err != nil {
196+
return errwrap.Wrapf(err, "could not replace subgraph")
197+
}
198+
199+
send(ctx, true) // send dummy and then close
200+
201+
continue
202+
203+
case <-ctx.Done():
204+
return nil
205+
}
206+
}
207+
}
208+
209+
func (obj *ExprIfFunc) replaceSubGraph(b bool) error {
210+
// delete the old subgraph
211+
if err := obj.init.Txn.Reverse(); err != nil {
212+
return errwrap.Wrapf(err, "could not Reverse")
213+
}
214+
215+
var f interfaces.Func
216+
if b {
217+
obj.init.Txn.AddGraph(obj.ThenGraph)
218+
f = obj.ThenFunc
219+
} else {
220+
obj.init.Txn.AddGraph(obj.ElseGraph)
221+
f = obj.ElseFunc
222+
}
223+
224+
// create the new subgraph
225+
edgeName := OutputFuncArgName
226+
edge := &interfaces.FuncEdge{Args: []string{edgeName}}
227+
obj.init.Txn.AddVertex(f)
228+
obj.init.Txn.AddEdge(f, obj.OutputVertex, edge)
229+
230+
return obj.init.Txn.Commit()
231+
}

0 commit comments

Comments
 (0)