-
Notifications
You must be signed in to change notification settings - Fork 0
/
fsm.go
166 lines (139 loc) · 3.45 KB
/
fsm.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
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
// Package yafsm is a simple library for building finite state machine.
package yafsm
import (
"fmt"
"sort"
"strings"
)
type State string
type States []State
type Callback func(t Transition, from State, to State) error
type TransitionConfig func(*config)
type config struct {
name string
callback Callback
}
func getConfig(options ...TransitionConfig) *config {
c := &config{}
for _, opt := range options {
opt(c)
}
return c
}
func getCallback(t Transition, c *config) Callback {
cb := c.callback
if cb == nil {
cb = t.GetCallback()
}
if cb == nil {
// noop
cb = func(t Transition, from, to State) error { return nil }
}
return cb
}
func createEdges(trans []Transition) map[string]Transition {
edges := make(map[string]Transition)
for _, t := range trans {
if len(t.From()) == 0 {
panic("Transition must have at least one from states")
}
for _, f := range t.From() {
key := fmt.Sprintf("(%s,%s)", string(f), string(t.To()))
if _, ok := edges[key]; ok {
panic(fmt.Sprintf("%s is a duplicate transition", key))
}
edges[key] = t
}
}
return edges
}
func (s State) String() string {
return strings.Title(string(s))
}
// NewStates creates a list of State from input.
func NewStates(states ...State) States {
ret := make(States, len(states))
for i, s := range states {
ret[i] = s
}
return ret
}
// Has checks existence of a given State.
func (states States) Has(s State) bool {
for _, state := range states {
if state == s {
return true
}
}
return false
}
func (states States) AsSortedStrings() []string {
ret := make([]string, len(states))
for i, s := range states {
ret[i] = string(s)
}
sort.Strings(ret)
return ret
}
type Transition interface {
Name() string
From() States
To() State
TransitionFrom(State, ...TransitionConfig) error
GetCallback() Callback
}
type transition struct {
name string
from States
to State
callback Callback
}
func (t transition) Name() string {
return t.name
}
func (t transition) From() States {
return t.from
}
func (t transition) To() State {
return t.to
}
func (t transition) GetCallback() Callback {
return t.callback
}
func (t transition) TransitionFrom(from State, options ...TransitionConfig) error {
return doTransition(createEdges([]Transition{t}), from, t.To(), options...)
}
// WithCallback sets a callback for a given transition.
func WithCallback(cb Callback) TransitionConfig {
return func(c *config) {
c.callback = cb
}
}
// WithName sets an optional name for the transition.
func WithName(name string) TransitionConfig {
return func(c *config) {
c.name = name
}
}
// NewTransition creates a new transition.
func NewTransition(from States, to State, options ...TransitionConfig) Transition {
c := getConfig(options...)
return &transition{c.name, from, to, c.callback}
}
// CreateTransitionHandler binds and returns an action handler for the given transitions.
func CreateTransitionHandler(trans []Transition) func(State, State, ...TransitionConfig) error {
edges := createEdges(trans)
return func(from, to State, options ...TransitionConfig) error {
return doTransition(edges, from, to, options...)
}
}
func doTransition(edges map[string]Transition, from, to State, options ...TransitionConfig) error {
c := getConfig(options...)
key := fmt.Sprintf("(%s,%s)", string(from), string(to))
tran := edges[key]
if tran == nil {
return fmt.Errorf(`"%s" -> "%s" is not a valid transition`, from, to)
}
cb := getCallback(tran, c)
return cb(tran, from, to)
}