-
Notifications
You must be signed in to change notification settings - Fork 3
/
state_machine.go
131 lines (119 loc) · 2.76 KB
/
state_machine.go
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
package fsm
import (
"errors"
"github.com/d5/tengo/v2"
)
// StateMachine represents a compiled state machine. Use Builder to
// construct and compile StateMachine.
type StateMachine struct {
invokeScript *tengo.Compiled
entryFns map[string]string
exitFns map[string]string
transitions map[string][]*transition
}
// Run executes the state machine from an initial state 'src' and an input data
// value 'in'. See
// https://github.com/d5/tengo/blob/master/docs/interoperability.md#type-conversion-table
// for data value conversions. Run continues to evaluate and move between
// states, until there are no more transitions available. When it stops, Run
// returns the final output value 'out' or an error 'err' if a script returned
// an error while executing.
func (m *StateMachine) Run(
src string,
in interface{},
) (out *tengo.Variable, err error) {
value, err := tengo.NewVariable("", in)
if err != nil {
return nil, err
}
for {
t, err := m.eval(src, value)
if err != nil {
return nil, err
}
if t == nil {
// no more transition
break
}
value, err = m.doTransition(src, t.dst, t.action, value)
if err != nil {
return nil, err
}
src = t.dst
}
return value, nil
}
func (m *StateMachine) eval(
src string,
in *tengo.Variable,
) (*transition, error) {
transitions, ok := m.transitions[src]
if !ok {
// no transition found
return nil, nil
}
for _, t := range transitions {
if t.condition == "" {
return t, nil
}
out, err := m.invoke(src, t.dst, t.condition, in)
if err != nil {
return nil, err
}
if out.Bool() {
return t, nil
}
}
return nil, nil // no transition found
}
func (m *StateMachine) doTransition(
src, dst, action string,
in *tengo.Variable,
) (*tengo.Variable, error) {
if exitFn := m.exitFns[src]; exitFn != "" {
out, err := m.invoke(src, dst, exitFn, in)
if err != nil {
return nil, err
}
if !out.IsUndefined() {
in = out
}
}
if action != "" {
out, err := m.invoke(src, dst, action, in)
if err != nil {
return nil, err
}
if !out.IsUndefined() {
in = out
}
}
if entryFn := m.entryFns[dst]; entryFn != "" {
out, err := m.invoke(src, dst, entryFn, in)
if err != nil {
return nil, err
}
if !out.IsUndefined() {
in = out
}
}
return in, nil
}
func (m *StateMachine) invoke(
src, dst, fn string,
in *tengo.Variable,
) (out *tengo.Variable, err error) {
_ = m.invokeScript.Set("src", &tengo.String{Value: src})
_ = m.invokeScript.Set("dst", &tengo.String{Value: dst})
_ = m.invokeScript.Set("fn", &tengo.String{Value: fn})
_ = m.invokeScript.Set("v", in.Object())
err = m.invokeScript.Run()
if err != nil {
return
}
out = m.invokeScript.Get("out")
if out, isErr := out.Object().(*tengo.Error); isErr {
return nil, errors.New(out.String())
}
return
}