Last active
January 11, 2020 15:23
-
-
Save matthewp/b1453605eb82fe22343144a36ecb2865 to your computer and use it in GitHub Desktop.
Zig Robot
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
zig-cache |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
const std = @import("std"); | |
fn LinkedList(comptime T: type) type { | |
return struct { | |
pub const Node = struct { | |
prev: ?*Node, | |
next: ?*Node, | |
data: T, | |
}; | |
first: ?*Node, | |
last: ?*Node, | |
len: usize, | |
}; | |
} | |
pub const Event = struct { | |
name: []const u8 | |
}; | |
pub fn Machine(comptime T: type) type { | |
return struct { | |
fn defaultGuard(data: *T) bool { | |
return true; | |
} | |
pub const Transition = struct { | |
from: []const u8, | |
to: []const u8, | |
guard_fn: fn (data: *T) bool = defaultGuard | |
}; | |
pub const State = struct { | |
name: []const u8, | |
transitions: []const Transition | |
}; | |
const Self = @This(); | |
const ListOfTransitions = LinkedList(Transition); | |
data: *T, | |
initial: State, | |
currentStates: []State, | |
pub fn init(data: *T) Self { | |
return Self{ | |
.initial = undefined, | |
.currentStates = &[_]State{}, | |
.data = data, | |
}; | |
} | |
pub fn transition(self: *Self, from: []const u8, to: []const u8) Transition { | |
return Transition{ | |
.from = from, | |
.to = to | |
}; | |
} | |
pub fn guard(self: *Self, t: *Transition, comptime gfn: fn (data: *T) bool) void { | |
t.guard_fn = gfn; | |
} | |
pub fn state(self: *Self, name: []const u8, transitions: []const Transition) State { | |
return State{ | |
.name = name, | |
.transitions = transitions | |
}; | |
} | |
pub fn states(self: *Self, ss: []State) void { | |
var initial = ss[0]; | |
self.initial = initial; | |
self.currentStates = ss; | |
} | |
fn transitionTo(self: *Self, current: State, candidates: ListOfTransitions, ev: Event) State { | |
// Run guards | |
var it = candidates.first; | |
while (it) |node| : (it = node.next) { | |
var t = node.data; | |
var guard_passed = t.guard_fn(self.data); | |
if(!guard_passed) { | |
continue; | |
} | |
for (self.currentStates) |s| { | |
if(std.mem.eql(u8, s.name, t.to)) { | |
return s; | |
} | |
} | |
} | |
return current; | |
} | |
pub fn send(self: *Self, current: State, ev: Event) State { | |
var transitions = current.transitions; | |
var new_state = current; | |
var candidates = ListOfTransitions{ | |
.first = null, | |
.last = null, | |
.len = 0 | |
}; | |
var i: usize = 0; | |
var node: ListOfTransitions.Node = undefined; | |
for (transitions) |t| { | |
if(std.mem.eql(u8, t.from, ev.name)) { | |
i += 1; | |
var last = node; | |
node = ListOfTransitions.Node{ | |
.prev = &last, | |
.next = null, | |
.data = t | |
}; | |
candidates.last = &node; | |
candidates.len = i; | |
if(candidates.first == null) { | |
candidates.first = &node; | |
} | |
} | |
} | |
return transitionTo(self, current, candidates, ev); | |
} | |
}; | |
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
const std = @import("std"); | |
const expectEqual = std.testing.expectEqual; | |
const robot = @import("robot.zig"); | |
test "Can do basic transitions" { | |
const Data = struct {}; | |
const MyMachine = robot.Machine(Data); | |
var machine = MyMachine.init(&Data{}); | |
var states = &[_]MyMachine.State { | |
machine.state("one", &[_]MyMachine.Transition { | |
machine.transition("next", "two") | |
}), | |
machine.state("two", &[_]MyMachine.Transition {}) | |
}; | |
machine.states(states); | |
var state = machine.initial; | |
state = machine.send(state, .{ .name = "next" }); | |
expectEqual(state.name, "two"); | |
} | |
test "A guard can prevent a transition" { | |
const Data = struct {}; | |
const MyMachine = robot.Machine(Data); | |
var machine = MyMachine.init(&Data{}); | |
var transition = machine.transition("next", "two"); | |
const return_false = struct { | |
fn returnFalse(d: *Data) bool { | |
return false; | |
} | |
}.returnFalse; | |
machine.guard(&transition, return_false); | |
const states = &[_]MyMachine.State { | |
machine.state("one", &[_]MyMachine.Transition { | |
transition | |
}), | |
machine.state("two", &[_]MyMachine.Transition {}) | |
}; | |
machine.states(states); | |
var state = machine.initial; | |
state = machine.send(state, .{ .name = "next" }); | |
expectEqual(state.name, "one"); | |
} | |
test "A guard can conditionally prevent a transition" { | |
const Data = struct { | |
pass: bool = false | |
}; | |
const MyMachine = robot.Machine(Data); | |
var data = Data{}; | |
var machine = MyMachine.init(&data); | |
var transition = machine.transition("next", "two"); | |
const my_guard = struct { | |
fn myGuard(d: *Data) bool { | |
return d.pass; | |
} | |
}.myGuard; | |
machine.guard(&transition, my_guard); | |
const states = &[_] MyMachine.State { | |
machine.state("one", &[_]MyMachine.Transition { | |
transition | |
}), | |
machine.state("two", &[_]MyMachine.Transition {}) | |
}; | |
machine.states(states); | |
var state = machine.initial; | |
state = machine.send(state, .{ .name = "next" }); | |
expectEqual(state.name, "one"); | |
data.pass = true; | |
state = machine.send(state, .{ .name = "next" }); | |
expectEqual(state.name, "two"); | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment