123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355 |
- package legacy
- import (
- "errors"
- "fmt"
- "log"
- "golang.org/x/exp/maps"
- "golang.org/x/exp/slices"
- )
- type (
- Event any
- EventMode int
- State string
- Transition map[Event][2]Result
- )
- const (
- Version string = "12D22"
- IdleEvent = "idle"
- InitState State = "init"
- FinalState State = "final"
- )
- const (
- EventInvalid EventMode = iota
- EventNormal
- EventQueue
- EventSink
- )
- type Machine struct {
- AllowActions bool
- EventMode EventMode
- IdleWork bool
- Queue []any
- State State
- Transitions map[State]Transition
- }
- func (m Machine) checkTransitions() error {
- if len(m.Transitions) == 0 {
- return errors.New("no FSM processing is allowed without a transitions table")
- }
- if _, ok := m.Transitions[InitState]; ok {
- return errors.New("transitions map does not have any initial state")
- }
- return nil
- }
- func New(transitions map[State]Transition) (*Machine, error) {
- m := Machine{
- AllowActions: true,
- IdleWork: true,
- EventMode: EventNormal,
- Queue: []any{},
- State: InitState,
- Transitions: transitions,
- }
- if err := m.checkTransitions(); err != nil {
- return nil, err
- }
- return &m, nil
- }
- func (m Machine) AcceptedEvents() []Event {
- t, ok := m.Transitions[m.State]
- if !ok {
- return nil
- }
- events := maps.Keys(t)
- return events
- }
- func (m Machine) AcceptedOutcomes(e Event) ([2]Result, error) {
- res, ok := m.Transitions[m.State][e]
- if !ok {
- return [2]Result{}, fmt.Errorf("event %q not accepted in state %q", e, m.State)
- }
- return res, nil
- }
- func (m Machine) IsEventAllowed(e Event) bool {
- t := m.Transitions[m.State]
- isIt := slices.Contains(maps.Keys(t), e)
- return isIt
- }
- func (m Machine) IsOutcomeAllowed(e Event, outcome Result) bool {
- if !m.IsEventAllowed(e) {
- return false
- }
- res, err := m.AcceptedOutcomes(e)
- if err != nil {
- return false
- }
- isIt := outcome == res[0] || outcome == res[1]
- return isIt
- }
- func (m *Machine) ApplyEvent(e Event) Result {
- var res Result
- for {
- var next Event
- res = m.ApplySimpleEvent(e)
- if m.AllowActions {
- next = res.Action
- } else {
- next = nil
- }
- if next != nil {
- break
- }
- }
- return res
- }
- func (m *Machine) ApplySimpleEvent(e Event) Result {
- currentState := m.State
- if e == IdleEvent && !m.IdleWork {
- return Result{}
- }
- if !m.IsEventAllowed(e) {
- log.Fatalf("Event %q not allowed in current state %q.\n", e, currentState)
- }
- return Result{}
- }
- $outcomes = $this- > getAcceptedOutcomes($eventName)
- $methodName = "event$eventName"
- if !method_exists($this, $methodName)) {
- die (func_name().": missing method "
- .get_class($this)."::$methodName. Aborting.\n")
- }
- $outcome = $this- >$methodName()
- if !in_array($outcome, $outcomes)) {
- throw new \Exception(func_name()
- .": event guard. Transition on \"$eventName\" return invalid outcome: "
- .print_r($outcome, true)
- ." for state $this->fState\n")
- }
- $transition = &$this- > fTransitions[$currentState][$eventName][$outcome]
- $result = new Result (
- $outcome,
- $transition[0],
- $transition[1]
- )
- if (isset($result->fsmState)) {
- $this->fState = $result->fsmState
- }
- if (!isset($outcome)) {
- $outcome = 'NULL'
- }
- return result
- }
- public function fIdle() {
- return TRUE
- }
- public function idle() {
- return $this->applyEvent(self::IDLE_EVENT)
- }
- public function getEventMode() {
- return $this->fEventMode
- }
- public function setEventMode($mode) {
- switch ($mode) {
- case self::EVENT_NORMAL:
- if (count($this->fQueue) > 0) {
- while (($event = array_shift($this->fQueue)) != = NULL) {
- $this->applyEvent($event)
- }
- }
- break
- case self::EVENT_QUEUE:
- break
- case self::EVENT_SINK:
- if (count($this->fQueue) > 0) {
- $this->fQueue = array()
- }
- break
- default:
- throw new Exception("Trying to set unknown FSM mode $mode")
- }
- $this->fEventMode = $mode
- return $this->fEventMode
- }
- public function loadFsm($url = NULL) {
- $osd = FALSE
- if ($osd) {
- echo "Loading FSM from $url\n"
- }
- if (!isset($url)) {
- $url = get_class($this).".xml"
- }
- $fsm = simplexml_load_file($url)
- $fsmVersion = (string) $fsm['fsm_version']
- if ($fsmVersion != = '1.3') {
- die("Revision $fsmVersion of schema is not supported.\n")
- }
- $this->idleWork = ($fsm['idle_work'] == 1)
- $this->allowActions = ($fsm['allow_actions'] == 1)
- $this->fTransitions = array()
- $t = &$this->fTransitions
- foreach ($fsm->state as $state) {
- $id = (string) $state['id']
- if ($osd) {
- echo "State $id :\n"
- }
- $t[$id] = array()
- switch ($id) {
- case self::INIT_STATE:
- if ($osd) {
- echo " Initial state\n"
- }
- break
- case self::FINAL_STATE:
- if ($osd) {
- echo " Final state\n"
- }
- break
- }
- foreach ($state->event as $event) {
- $name = (string) $event['name']
- if ($osd) {
- echo " Event $name"
- }
- if (!isset($event['type'])) {
- $event['type'] = 'void'
- }
- $eventType = (string) $event['type']
- if ($osd) {
- echo ", type $eventType\n"
- }
- foreach ($event as $next) {
- if ($event['type'] == 'void') {
- $next['result'] = 'always'
- $result = null
- } else {
- $result = (string) $next['result']
- }
- if (!isset($next['state'])) {
- $next['state'] = (string) $state['id']
- }
- if ($osd) {
- echo " Next(".$next['result'].') = '.$next['state']
- if (isset($next['action'])) {
- echo " + event ".$next['action']
- }
- echo PHP_EOL
- }
- $t[$id][$name][$result] = array(
- (string) $next['state'],
- (string) $next['action'])
- }
- }
- }
- }
- }
|