Go State Machine Library

Generate native Go state machines from W3C SCXML with Go 1.22+ generics. Full 202/202 W3C conformance tests passing. Pure Go, no CGo required.

State Machines in Go

Go's type system with generics (Go 1.18+) and interface-based polymorphism provides a clean foundation for state machine implementations. However, hand-writing complex state machines with hierarchical states, parallel regions, and history states requires significant boilerplate that is difficult to maintain as complexity grows.

Existing approaches compared

Approach Pros Cons
switch/case Simple, native Go Manual, no hierarchy, no parallel states
looplab/fsm Popular, callback-based String-typed states, no hierarchy, runtime only
qmuntal/stateless Fluent API, guards No parallel states, no standard compliance
SCE W3C standard, full hierarchy, AOT generated, generics SCXML definition required, external code generation step

SCE takes a unique approach in the Go ecosystem: instead of a runtime-configured DSL, you define state machines in W3C SCXML (the international standard for statecharts), and SCE's code generator produces idiomatic Go code at build time. This gives you the full power of the W3C specification -- hierarchical states, parallel regions, history, invoke, delayed events -- with type-safe generated code using Go generics.

How It Works

Step 1: Define your state machine in SCXML

<?xml version="1.0" encoding="UTF-8"?>
<scxml xmlns="http://www.w3.org/2005/07/scxml" version="1.0"
       name="traffic_light" initial="red">
  <state id="red">
    <transition event="timer" target="green"/>
  </state>
  <state id="green">
    <transition event="timer" target="yellow"/>
  </state>
  <state id="yellow">
    <transition event="timer" target="red"/>
  </state>
</scxml>

Step 2: Generate Go code

# Generate Go state machine (-l go or --language go)
sce-codegen generate --scxml traffic_light.scxml --language go --output-dir ./generated/

The generator produces a Go file containing a State type (iota constants), an Event type, and a Policy struct that implements the StatePolicy[S, E] interface. The Engine[S, E] generic type from sce-go-runtime drives execution.

Step 3: Use in your Go code

package main

import (
    sce "github.com/newmassrael/sce-go-runtime"
    gen "your-module/generated"
)

func main() {
    // Create the policy (generated from SCXML) and engine
    policy := gen.NewTrafficLightPolicy()
    engine := sce.NewEngine[gen.TrafficLightState, gen.TrafficLightEvent](policy)

    engine.Initialize()                                    // Enters "red" state
    engine.ProcessEvent(gen.TrafficLightEventTimer)         // red -> green
    engine.ProcessEvent(gen.TrafficLightEventTimer)         // green -> yellow
    engine.ProcessEvent(gen.TrafficLightEventTimer)         // yellow -> red

    fmt.Println(engine.GetCurrentState() == gen.TrafficLightStateRed) // true
}

ProcessEvent() is a convenience method that combines RaiseExternal() + Step(). For fine-grained control, use them separately:

// Low-level API: queue event, then process explicitly
engine.RaiseExternal(gen.TrafficLightEventTimer, "", "")
engine.Step()

W3C SCXML Features in Go

SCE's Go backend passes all 202 mandatory W3C SCXML conformance tests, supporting the full specification:

Feature W3C Section Status
Compound (hierarchical) states 3.3 Supported
Parallel states 3.4 Supported
History states (shallow + deep) 3.10 Supported
ECMAScript datamodel B.2 Supported
Invoke (parent-child) 6.4 Supported
Delayed send 6.2 Supported

Architecture

The Go backend consists of three modules with clear separation of concerns:

Module Purpose Dependencies
sce-go-runtime StatePolicy[S, E] interface, Engine[S, E] generic engine Go stdlib only
sce-go-lua Lua 5.4 script engine via Shopify/go-lua sce-go-runtime, go-lua
sce-go-tests W3C SCXML conformance test suite (202 tests) sce-go-runtime, sce-go-lua

The runtime module has zero external dependencies -- it uses only the Go standard library. The Lua engine uses Shopify/go-lua, a pure Go implementation of Lua that requires no C compiler or CGo. This makes the entire stack cross-compilable to any platform Go supports.

Why SCXML for Go?

Comparison with Go State Machine Libraries

Feature switch/case looplab/fsm qmuntal/stateless SCE
Hierarchical states Manual No Yes (substates) Yes
Parallel states Manual No No Yes
History states Manual No No Yes
Guarded transitions Manual No Yes Yes
Delayed events Manual No No Yes
Standard compliance None None None W3C SCXML 1.0
Type safety Manual String-typed Interface-typed Generic (comparable)
Cross-language No No No C++, Kotlin, Rust, Go
Visual tooling No No No Browser visualizer

Getting Started

# Clone the repository
git clone --recursive https://github.com/newmassrael/scxml-core-engine.git
cd scxml-core-engine

# Generate Go state machine from SCXML
sce-codegen generate --scxml your_machine.scxml --language go --output-dir ./generated/

# Run the Go W3C test suite
cd sce-go-tests && go test ./...

The Go runtime (sce-go-runtime/) provides the Engine[S, E] type and StatePolicy[S, E] interface. Test infrastructure is in sce-go-tests/. The code generator produces standalone Go files that depend only on the runtime module.