schigh / state

state machines

Geek Repo:Geek Repo

Github PK Tool:Github PK Tool

state

state implements lightweight and concurrent state machines for various uses.

TL;DR examples

The following example is the classic use case for a finite state machine. The machine evaluates if a series of characters match the pattern a+bc.

A start state is created, as well as states for all acceptable transitions. The c state is set as one of the end states (there can be more than one).

For each string, we reset the state machine and feed it into the start state, popping the first character off for each evaluation, until we either reach an invalid state, or there are no more characters to evaluate.

package main
import (
    "context"
    "fmt"
    
    "github.com/schigh/state/fsm"
)

func main() {
    s1 := fsm.NewState("start")
    s2 := fsm.NewState("a")
    s3 := fsm.NewState("b")
    s4 := fsm.NewState("c")

    // the start state.  since this will be the first one added,
    // it will be set as the start state implicitly
    // this state will transition to the next state if the first
    // character in our word is 'a'
    case1 := s1.When("starts with a", func(ctx context.Context, v interface{}) ( bool,  error) {
        s, _ := v.(string)
        return len(s) > 0 && s[0] == 'a', nil
    }).Then(s2)

    // this state will transition to the next state if the
    // first character in our word is 'b'
    case2 := s2.When("head is b", func(ctx context.Context, v interface{}) ( bool,  error) {
        s, _ := v.(string)
        return len(s) > 0 && s[0] == 'b', nil
    }).Then(s3)

    // this condition represents a loop in the 'a' state, where any string that starts
    // with 1...N instances of 'a' is valid
    case3 := s2.When("head is a", func(ctx context.Context, v interface{}) ( bool,  error) {
        s, _ := v.(string)
        return len(s) > 0 && s[0] == 'a', nil
    }).Then(s2)

    // this will transition to the final state 'c'
    case4 := s3.When("head is c", func(ctx context.Context, v interface{}) ( bool,  error) {
        s, _ := v.(string)
        return len(s) > 0 && s[0] == 'c', nil
    }).Then(s4)

    machine := fsm.NewMachine(fsm.WithTransitions(case1, case2, case3, case4))
    if err := machine.SetEndStates("c"); err != nil {
        panic(err)
    }
    if err := machine.Validate(); err != nil {
        panic(err)
    }

    words := []string{
        "abc",
        "cba",
        "aaaaaabc",
        "aaababc",
        "ab",
    }
    ctx := context.Background()

    for _, word := range words {
        if resetErr := machine.Reset(); resetErr != nil {
            panic(resetErr)
        }

        valid := true
        var err error

        test := word
        for valid {
            valid, err = machine.Update(ctx, test)
            if err != nil {
                panic(err)
            }
            if len(test) <= 1 {
                break
            }
            test = test[1:]
        }

        if valid && machine.IsEndState(){
            fmt.Printf("'%s' matches the pattern\n", word)
            continue
        }
        fmt.Printf("'%s' DOES NOT MATCH the pattern\n", word)
    }
}

This is the output from the above example:

'abc' matches the pattern
'cba' DOES NOT MATCH the pattern
'aaaaaabc' matches the pattern
'aaababc' DOES NOT MATCH the pattern
'ab' DOES NOT MATCH the pattern

Packages

fsm

The fsm package implements a more traditional finite state machine, where the transition from one state to another is triggered by a targeted evaluation.
See the README for detailed docs.

flipflop

The flipflop package implements a set of toggle switches, which can trigger downstream events when a toggle occurs. See the README for more details.

About

state machines


Languages

Language:Go 99.5%Language:Makefile 0.5%