(* I want to keep everything in the same file, but split up into reusable FSM and test TestFSM modules Anything referring to states A and B or events E and F belongs in TestFSM *) MODULE FSM; CONST Init* = 0; Null* = 0; TYPE State* = INTEGER; StateName* = ARRAY 8 OF CHAR; Event* = SET; EventName* = ARRAY 8 OF CHAR; Machine* = POINTER TO MachineDesc; MachineDesc* = RECORD currState*, prevState*: State; NewState*: BOOLEAN; ChangeState*: PROCEDURE (m: Machine; x: State); UpdateStateVars*: PROCEDURE (m: Machine); GetStateName*: PROCEDURE (m: Machine; s: State; VAR result: StateName); PrintEventNames*: PROCEDURE (e: Event); END; Handler* = PROCEDURE (m: Machine; e: Event); PROCEDURE ChangeState(m: Machine; x: State); BEGIN m.currState := x END ChangeState; PROCEDURE InitMachine*(m: Machine); BEGIN m.ChangeState := ChangeState; END InitMachine; END FSM. MODULE TestFSM; IMPORT Dbg, FSM; CONST StateA = 1; StateB = 2; LastState = StateB; EventE = 1; EventF = 2; TYPE TestMachine = POINTER TO TestMachineDesc; TestMachineDesc = RECORD (FSM.MachineDesc) END; VAR stateNames: ARRAY 3 OF FSM.StateName; evtNames: ARRAY 3 OF FSM.EventName; handlers: ARRAY 3 OF FSM.Handler; PROCEDURE GetStateName(m: FSM.Machine; s: FSM.State; VAR result: FSM.StateName); BEGIN CASE m OF TestMachine: result := stateNames[s]; END END GetStateName; PROCEDURE PrintEventNames(e: FSM.Event); VAR i: INTEGER; BEGIN FOR i := 0 TO 31 DO IF i IN e THEN (* I use facility Local0 for this module *) Dbg.Puts(Dbg.Local0, evtNames[i]); Dbg.Putchar(Dbg.Local0, " "); END END END PrintEventNames; PROCEDURE UpdateStateVars(m: FSM.Machine); VAR nm: FSM.StateName; BEGIN CASE m OF TestMachine: m.NewState := (m.currState # m.prevState); IF m.NewState THEN m.GetStateName(m, m.prevState, nm); Dbg.Puts(Dbg.Local0, nm); Dbg.Puts(Dbg.Local0, " -> "); m.GetStateName(m, m.currState, nm); Dbg.Puts(Dbg.Local0, nm); Dbg.WriteLn(Dbg.Local0) END; m.prevState := m.currState END END UpdateStateVars; PROCEDURE NewTestMachine(): TestMachine; VAR result: TestMachine; BEGIN NEW(result); FSM.InitMachine(result); result.GetStateName := GetStateName; result.PrintEventNames := PrintEventNames; result.UpdateStateVars := UpdateStateVars; result.currState := StateA; RETURN result END NewTestMachine; PROCEDURE InA(m: FSM.Machine; e: FSM.Event); BEGIN CASE m OF TestMachine: IF m.NewState THEN END; IF EventE IN e THEN m.ChangeState(m, StateB); END END END InA; PROCEDURE InB(m: FSM.Machine; e: FSM.Event); BEGIN CASE m OF TestMachine: IF m.NewState THEN END; IF EventF IN e THEN m.ChangeState(m, StateA) ELSE (* Could put "IF ~CommonEventWasHandled(e) THEN" here *) Dbg.Puts(Dbg.Local0, "Bad event"); Dbg.WriteLn(Dbg.Local0) END END END InB; PROCEDURE TestMachineEvent(m: TestMachine; e: FSM.Event); VAR hdl: FSM.Handler; BEGIN Dbg.Puts(Dbg.Local0, "TestMachineEvent: e "); m.PrintEventNames(e); Dbg.WriteLn(Dbg.Local0); m.UpdateStateVars(m); REPEAT IF (m.currState < FSM.Init) OR (m.currState > LastState) THEN Dbg.Puts(Dbg.Local0, "Bad state "); Dbg.PrintHexint(Dbg.Local0, m.currState); Dbg.WriteLn(Dbg.Local0) ELSE hdl := handlers[m.currState]; IF hdl # NIL THEN hdl(m, e); END END; e := {FSM.Null}; m.UpdateStateVars(m) UNTIL ~m.NewState; END TestMachineEvent; (* This PROCEDURE is only for testing *) PROCEDURE Run*; VAR m: TestMachine; BEGIN m := NewTestMachine(); TestMachineEvent(m, {FSM.Null}); TestMachineEvent(m, {EventE}); TestMachineEvent(m, {EventF}); Dbg.WriteLn(Dbg.Local0); END Run; BEGIN stateNames[FSM.Init] := "Init"; stateNames[StateA] := "A"; stateNames[StateB] := "B"; evtNames[FSM.Null] := "Null"; evtNames[EventE] := "E"; evtNames[EventF] := "F"; handlers[FSM.Init] := NIL; handlers[StateA] := InA; handlers[StateB] := InB; Dbg.Enable(Dbg.Local0); END TestFSM.