- Notifications
You must be signed in to change notification settings - Fork6
A finite state machine library for Zig
License
cryptocode/zigfsm
Folders and files
| Name | Name | Last commit message | Last commit date | |
|---|---|---|---|---|
Repository files navigation
zigfsm is afinite state machine library for Zig.
This library supports Zig 0.12.x, 0.13, 0.14.x, and 0.15.1.
Use the zigfsm main branch to compile with Zig master. Use the appropriate zig-version tag to target a specific Zig version.
Tested on Linux, macOS, FreeBSD and Windows.
- Features
- Motivation
- Using zigfsm
- Building
- Importing the library
- Learning from the tests
- Creating a state machine type
- Making an instance
- Adding state transitions
- Optionally defining events
- Defining transitions and events at the same time
- Defining transitions and events as a table
- Changing state
- Probing the current state
- Inspecting what transition happened
- Valid states iterator
- Importing state machines
- Transition handlers
- Never allocates
- Works at both comptime and runtime
- Fast transition validation
- Compact memory representation
- State machines can export themselves to the Graphviz DOT format
- Defined programmatically or by importing Graphviz or libfsm text (even at compile time)
- Imported state machines can autogenerate state- and event enums
- Optional event listeners can add functionality and cancel transitions
- Push-down automaton wrappers are easy to write (see GameState example in tests.zig)
- Comprehensive test coverage which also serves as examples
Using an FSM library may have some benefits over hand-written state machines:
- Many real-world processes, algorithms, and protocols have rigorously defined state machines available. These can be imported directly or programmatically into zigfsm.
- Can lead to significant simplification of code, as transition rules are explicitly stated in one place. Contrast this with the brittleness of manually checking and documenting which states can follow a certain state when a certain event happens.
- An invalid state transition is an immediate error with useful contextual information.
- You get visualization for free, which is helpful during development, debugging and as documentation.
Before diving into code, it's worth repeating that zigfsm state machines can generate their own diagram, as well as import them. This can be immensely helpful when working on your state machines,as you get a simple visualization of all transitions and events. Obviously, the diagrams can be used as part of your documentation as well.
Here's the diagram from the CSV parser test, as generated by the library:
Diagrams can be exported to any writer usingexportGraphviz(...), which acceptsStateMachine.ExportOptions to change style and layout.
A png can be produced using the following command:dot -Tpng csv.gv -o csv.png
To build, test and benchmark:
zig build -Doptimize=ReleaseFastzig build testzig build benchmarkThe benchmark always runs under ReleaseFast.
Add zigfsm as a Zig package to yourzon file, or simply importmain.zig directly if vendoring.
Here's how to update your zon file using the latest commit of zigfsm:
zig fetch --save git+https://github.com/cryptocode/zigfsm
Next, update yourbuild.zig to add zigfsm as an import. For example:
exe.root_module.addImport("zigfsm",b.dependency("zigfsm", .{}).module("zigfsm"));
Now you can import zigfsm from any Zig file:
// This example implements a simple Moore machine: a three-level intensity lightswitchconststd=@import("std");constzigfsm=@import("zigfsm");pubfnmain()!void {// A state machine type is defined using state enums and, optionally, event enums.// An event takes the state machine from one state to another, but you can also switch to// other states without using events.//// State and event enums can be explicit enum types, comptime generated enums, or// anonymous enums like in this example.//// If you don't want to use events, simply pass null to the second argument.// We also define what state is the initial one, in this case .offvarfsm=zigfsm.StateMachine(enum {off,dim,medium,bright },enum {click },.off).init();// There are many ways to define transitions (and optionally events), including importing// from Graphviz. In this example we use a simple API to add events and transitions.tryfsm.addEventAndTransition(.click,.off,.dim);tryfsm.addEventAndTransition(.click,.dim,.medium);tryfsm.addEventAndTransition(.click,.medium,.bright);tryfsm.addEventAndTransition(.click,.bright,.off);std.debug.assert(fsm.isCurrently(.off));// Do a full cycle: off -> dim -> medium -> bright -> off_=tryfsm.do(.click);_=tryfsm.do(.click);_=tryfsm.do(.click);_=tryfsm.do(.click);// Make sure we're in the expected statestd.debug.assert(fsm.isCurrently(.off));std.debug.assert(fsm.canTransitionTo(.dim));}
A good way to learn zigfsm is to study thetests file.
This file contains a number of self-contained tests that also demonstrates various aspects of the library.
A state machine type is defined using state enums and, optionally, event enums.
Here we create an FSM for a button that can be clicked to flip between on and off states. The initial state is.off:
constState=enum {on,off };constEvent=enum {click };constFSM=zigfsm.StateMachine(State,Event,.off);
If you don't need events, simply pass null:
constFSM=zigfsm.StateMachine(State,null,.off);
Now that we have a state machinetype, let's create an instance with an initial state :
varfsm=FSM.init();
If you don't need to reference the state machine type, you can define the type and get an instance like this:
varfsm=zigfsm.StateMachine(State,Event,.off).init();
You can also pass anonymous state/event enums:
varfsm=zigfsm.StateMachine(enum {on,off },enum {click },.off).init();
tryfsm.addTransition(.on,.off);tryfsm.addTransition(.off,.on);
WhiletransitionTo can now be used to change state, it's also common to invoke state transitionsusing events. This can vastly simplify using and reasoning about your state machine.
The same event can cause different transitions to happen, depending on the current state.
Let's define what.click means for the on and off states:
tryfsm.addEvent(.click,.on,.off);tryfsm.addEvent(.click,.off,.on);
This expresses that if.click happens in the.on state, then transition to the.off state, and vice versa.
A helper function is available to define events and state transitions at the same time:
tryfsm.addEventAndTransition(.click,.on,.off);tryfsm.addEventAndTransition(.click,.off,.on);
Which approach to use depends on the application.
Rather than calling addTransition and addEvent,StateMachineFromTable can be used to pass a table of event- and state transitions.
constState=enum {on,off };constEvent=enum {click };constdefinition= [_]Transition(State,Event){ .{ .event=.click, .from=.on, .to=.off }, .{ .event=.click, .from=.off, .to=.on },};varfsm=zigfsm.StateMachineFromTable(State,Event,&definition,.off, &.{}).init();
Note that the.event field is optional, in which case only transition validation is added.
Let's flip the lights on by directly transitioning to the on state:
tryfsm.transitionTo(.on);
This will fail withStateError.Invalid if the transition is not valid.
Next, let's change state using the click event. In fact, let's do it several times, flipping the switch off and on and off again:
tryfsm.do(.click);tryfsm.do(.click);tryfsm.do(.click);
Again, this will fail withStateError.Invalid if a transition is not valid.
Finally, it's possible to change state through the more genericapply function, which takes either a new state or an event.
tryfsm.apply(.{ .state=.on });tryfsm.apply(.{ .event=.click });
The current state is available throughcurrentState(). To check if the current state is a specific state, callisCurrently(...)
If final states have been added throughaddFinalState(...), you can check if the current state is in a final state by callingisInFinalState()
To check if the current state is in the start state, callisInStartState()
See the API docstring for more information about these are related functions.
consttransition=tryfsm.do(.identifier);if (transition.to==.jumpingandtransition.from==.running) {...}
... wheretransition contains the fieldsfrom,to andevent.
Followed by an if/else chain that checks relevant combinations of from- and to states. This could, as an example, be used in a parser loop.
See the tests for examples.
It's occasionally useful to know which states are possible to reach from the current state. This is done using an iterator:
while (fsm.validNextStatesIterator())|valid_next_state| {...}
It's possible, even at compile time, to parse aGraphviz orlibfsm text file and create a state machine from this.
importTextis used when you already have state- and event enums defined in Zig.importTextcan also be called at runtime to define state transitions.generateStateMachineFromTextis used when you want the compiler to generate these enums for you. While this saves you from writing enums manually, a downside is that editors and language servers are unlikely to support autocomplete on generated types.
The source input can be a string literal, or brought in by@embedFile.
See the test cases for examples on how to use the import features.
A previous section explained how to inspect the source and target state. There's another way to do this, using callbacks.
This gets called when a transition happens. The main benefit is that it allows you to cancel a transition.
Handlers also makes it easy to keep additional state, such as source locations when writing a parser.
Let's keep track of the number of times a light switch transition happens:
varcountingHandler=CountingHandler.init();tryfsm.addTransitionHandler(&countingHandler.handler);
Whenever a transition happens, the handler's publiconTransition function will be called. See tests for completeexamples of usage.
The transition handler can conditionally stop a transition from happening by returningHandlerResult.Cancel. The callsite oftransitionTo ordo will then fail withStateError.Invalid
Alternatively,HandlerResult.CancelNoError can be used to cancel without failure (in other words, the current state remains but the callsite succeeds)
About
A finite state machine library for Zig
Topics
Resources
License
Uh oh!
There was an error while loading.Please reload this page.
Stars
Watchers
Forks
Packages0
Contributors5
Uh oh!
There was an error while loading.Please reload this page.
