Skip to content

Commit

Permalink
Back out the error passing, in practive it did not work out well, bet…
Browse files Browse the repository at this point in the history
…ter for the user to handle it.
  • Loading branch information
Marek Dolgos committed Jan 13, 2016
1 parent c52a6e9 commit c4f5c67
Show file tree
Hide file tree
Showing 2 changed files with 48 additions and 128 deletions.
110 changes: 37 additions & 73 deletions dfa.go
Original file line number Diff line number Diff line change
Expand Up @@ -5,19 +5,6 @@ import (
"fmt"
)

type Errors []error

func (e Errors) Error() string {
var buf bytes.Buffer
for i, err := range e {
buf.WriteString(err.Error())
if i < len(e)-1 {
buf.WriteString(", ")
}
}
return buf.String()
}

type State string

func (s State) String() string {
Expand All @@ -31,16 +18,15 @@ func (l Letter) String() string {
}

type DFA struct {
q map[State]bool // States
e map[Letter]bool // Alphabet
d map[domainelement]*codomainelement // Transition
q0 State // Start State
f map[State]bool // Terminal States
synccall bool // Call callbacks synchronously
done chan laststate // Termination channel
input *Letter // Inputs to the DFA
stop chan struct{}
logger func(State) // Logger for transitions
q map[State]bool // States
e map[Letter]bool // Alphabet
d map[domainelement]*codomainelement // Transition
q0 State // Start State
f map[State]bool // Terminal States
done chan laststate // Termination channel
input *Letter // Inputs to the DFA
stop chan struct{} // Stops the DFA
logger func(State) // Logger for transitions
}

type domainelement struct {
Expand All @@ -54,8 +40,8 @@ type codomainelement struct {
}

type laststate struct {
s State
errors Errors
s State
accepted bool
}

func New() *DFA {
Expand All @@ -80,16 +66,16 @@ func (m *DFA) SetTransition(from State, input Letter, to State, exec interface{}
panic("state cannot be defined as the empty string")
}
switch exec.(type) {
case func() error:
case func():
if !m.f[to] {
panic(fmt.Sprintf("stateful computation must be of type 'func() (Letter, error)' for non-terminal '%v' state", to))
panic(fmt.Sprintf("stateful computation must be of type 'func() Letter' for non-terminal '%v' state", to))
}
case func() (Letter, error):
case func() Letter:
if m.f[to] {
panic(fmt.Sprintf("stateful computation must be of type 'func() error' for terminal '%v' state", to))
panic(fmt.Sprintf("stateful computation must be of type 'func()' for terminal '%v' state", to))
}
default:
panic("stateful computation must be of type 'func() error' or 'func() (Letter, error)")
panic("stateful computation must be of type 'func()' or 'func() Letter")
}
m.q[to] = true
m.q[from] = true
Expand Down Expand Up @@ -136,8 +122,8 @@ func (m *DFA) Alphabet() []Letter {
}

// Run the DFA, blocking until Stop is called or the DFA enters a terminal state.
// Returns the terminal state and any errors.
func (m *DFA) Run(init interface{}) (State, error) {
// Returns the last state and true is the last state was a terminal state.
func (m *DFA) Run(init interface{}) (State, bool) {
// Check some pre-conditions.
if init == nil {
panic("initial stateful computation is nil")
Expand All @@ -159,32 +145,25 @@ func (m *DFA) Run(init interface{}) (State, error) {
// Run the DFA.
go func() {
defer close(m.done)
var errors Errors
// The current state, starts at q0.
s := m.q0
// Run the initial stateful computation.
if m.f[s] {
// If the state is a terminal state then the DFA has
// accepted the input sequence and it can stop.
m.done <- laststate{s, errors}
m.done <- laststate{s, true}
return
} else {
// Otherwise continue reading generated input
// by starting the next stateful computation.
switch init := init.(type) {
case func() error:
case func():
m.logger(s)
if err := init(); err != nil {
errors = append(errors, err)
}
case func() (Letter, error):
init()
case func() Letter:
m.logger(s)
if l, err := init(); err != nil {
errors = append(errors, err)
m.input = &l
} else {
m.input = &l
}
l := init()
m.input = &l
}
}
for {
Expand All @@ -201,9 +180,7 @@ func (m *DFA) Run(init interface{}) (State, error) {
l := *m.input
// Reject upfront if letter is not in alphabet.
if !m.e[l] {
errors = append(errors, fmt.Errorf("letter '%v' is not in alphabet", l))
m.done <- laststate{s, errors}
return
panic(fmt.Sprintf("letter '%v' is not in alphabet", l))
}
// Compose the domain element, so that the co-domain
// element can be found via the transition function.
Expand All @@ -212,43 +189,34 @@ func (m *DFA) Run(init interface{}) (State, error) {
if coe := m.d[de]; coe != nil {
s = coe.s
switch exec := coe.exec.(type) {
case func() error:
case func():
m.logger(s)
if err := exec(); err != nil {
errors = append(errors, err)
}
case func() (Letter, error):
exec()
case func() Letter:
m.logger(s)
if l, err := exec(); err != nil {
errors = append(errors, err)
m.input = &l
} else {
m.input = &l
}
l := exec()
m.input = &l
}
if m.f[s] {
// If the new state is a terminal state then
// the DFA has accepted the input sequence
// and it can stop.
m.done <- laststate{s, errors}
m.done <- laststate{s, true}
return
}
} else {
// Otherwise stop the DFA with a rejected state,
// the DFA has rejected the input sequence.
errors = append(errors, fmt.Errorf("no state transition for input '%v' from '%v'", l, s))
m.done <- laststate{s, errors}
return
panic(fmt.Sprintf("no state transition for input '%v' from '%v'", l, s))
}
}
}
// The caller has closed the input channel, check if the
// current state is accepted or rejected by the DFA.
if m.f[s] {
m.done <- laststate{s, errors}
m.done <- laststate{s, true}
} else {
errors = append(errors, fmt.Errorf("state '%v' is not terminal", s))
m.done <- laststate{s, errors}
m.done <- laststate{s, false}
}
}()
return m.result()
Expand Down Expand Up @@ -278,11 +246,7 @@ func (m *DFA) GraphViz() string {
return buf.String()
}

func (m *DFA) result() (State, error) {
func (m *DFA) result() (State, bool) {
t := <-m.done
if len(t.errors) == 0 {
return t.s, nil
} else {
return t.s, t.errors
}
}
return t.s, t.accepted
}
66 changes: 11 additions & 55 deletions dfa_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -22,20 +22,19 @@ type Exec struct {
cmu *sync.Mutex
}

func (e *Exec) Next() (Letter, error) {
func (e *Exec) Next() Letter {
e.cmu.Lock()
defer e.cmu.Unlock()
l := e.Sequence[0]
e.Sequence = e.Sequence[1:]
e.nextCount++
return l, nil
return l
}

func (e *Exec) Last() error {
func (e *Exec) Last() {
e.cmu.Lock()
defer e.cmu.Unlock()
e.lastCount++
return nil
}

func (e *Exec) NextCount() int {
Expand Down Expand Up @@ -117,9 +116,9 @@ func TestSimple(t *testing.T) {
d.SetTransition(Resending, SendFailure, Resending, e.Next)
d.SetTransition(Resending, ExitWanted, Exiting, e.Last)

final, err := d.Run(e.Next)
if err != nil {
t.Fatalf("failed to run dfa: %v", err)
final, accepted := d.Run(e.Next)
if !accepted {
t.Fatalf("failed to recognize Exiting or Terminating as terminal state")
}

if final != Exiting {
Expand Down Expand Up @@ -151,9 +150,9 @@ func TestLastState(t *testing.T) {
// The expectation is that the Next nor Last method will
// ever be called since the DFA's starting and terminal
// state are the same.
final, err := d.Run(e.Next)
if err != nil {
t.Fatalf("failed to run dfa: %v", err)
final, accepted := d.Run(e.Next)
if !accepted {
t.Fatalf("failed to recognize Starting as terminal state")
}
if final != Starting {
t.Fatalf("final state should have been: %v, but was: %v", Starting, final)
Expand Down Expand Up @@ -189,8 +188,8 @@ func TestGraphViz(t *testing.T) {
d.SetStartState(Starting)
d.SetTerminalStates(Exiting, Terminating)

next := func() (Letter, error) { return Exit, nil }
exit := func() error { return nil }
next := func() Letter { return Exit }
exit := func() {}

d.SetTransition(Starting, EverybodyStarted, Running, next)
d.SetTransition(Starting, Failure, Exiting, exit)
Expand Down Expand Up @@ -240,46 +239,3 @@ func TestGraphViz(t *testing.T) {
t.Fatalf("expected string: `%v`", `"finishing" -> "exiting"[label="exit"];`)
}
}

func TestErrors(t *testing.T) {
// States
Starting := State("starting")
Running := State("running")
Exiting := State("exiting")
// Letters
Failure := Letter("failure")
Success := Letter("success")

d := New()
d.SetStartState(Starting)
d.SetTerminalStates(Exiting)

starting := func() (Letter, error) { return Success, nil }
running := func() (Letter, error) { return Failure, fmt.Errorf("error from running") }
exiting := func() error { return fmt.Errorf("error from exiting") }

d.SetTransition(Starting, Success, Running, running)
d.SetTransition(Starting, Failure, Exiting, exiting)

d.SetTransition(Running, Success, Exiting, exiting)
d.SetTransition(Running, Failure, Exiting, exiting)

final, err := d.Run(starting)
if err == nil {
t.Fatalf("expected non-nil error")
}
if final != Exiting {
t.Fatalf("expected final state to be Exiting")
}
switch err := err.(type) {
case Errors:
if "error from running" != err[0].Error() {
t.Fatalf("expected 1st error to be 'error from running'")
}
if "error from exiting" != err[1].Error() {
t.Fatalf("expected 2nd error to be 'error from exiting'")
}
default:
t.Fatalf("expected error of type Errors")
}
}

0 comments on commit c4f5c67

Please sign in to comment.