about summary refs log tree commit diff
diff options
context:
space:
mode:
authorVincent Ambo <mail@tazj.in>2018-09-26T09·36+0200
committerVincent Ambo <mail@tazj.in>2018-09-26T14·54+0200
commit60824a06f1db981a07d738c71ba65c965a473838 (patch)
tree41e493c4edf16c0fa0c7b76f16da9d06af5edf26
parentda66599696dce1378e6fcef6a5149ba60e5006a2 (diff)
feat(door): Check in example door implementation
Checks in my classic, lockable door example implemented in Finito.

This does not yet contain the documentation of the door in the Haskell
version of Finito.
-rw-r--r--Cargo.toml3
-rw-r--r--finito-door/Cargo.toml7
-rw-r--r--finito-door/src/lib.rs144
3 files changed, 153 insertions, 1 deletions
diff --git a/Cargo.toml b/Cargo.toml
index bb5cb6b67094..5d62cea6ec2f 100644
--- a/Cargo.toml
+++ b/Cargo.toml
@@ -1,4 +1,5 @@
 [workspace]
 members = [
-  "finito-core"
+  "finito-core",
+  "finito-door"
 ]
diff --git a/finito-door/Cargo.toml b/finito-door/Cargo.toml
new file mode 100644
index 000000000000..3497a1c04cca
--- /dev/null
+++ b/finito-door/Cargo.toml
@@ -0,0 +1,7 @@
+[package]
+name = "finito-door"
+version = "0.1.0"
+authors = ["Vincent Ambo <vincent@aprila.no>"]
+
+[dependencies.finito]
+path = "../finito-core"
diff --git a/finito-door/src/lib.rs b/finito-door/src/lib.rs
new file mode 100644
index 000000000000..a4aad8dcbbe3
--- /dev/null
+++ b/finito-door/src/lib.rs
@@ -0,0 +1,144 @@
+//! TODO: port the door docs
+
+extern crate finito;
+
+use finito::FSM;
+
+type Code = usize;
+type Attempts = usize;
+
+#[derive(Debug, PartialEq)]
+pub enum DoorState {
+    /// This state represents an open door.
+    Opened,
+
+    /// This state represents a closed door.
+    Closed,
+
+    /// This state represents a locked door on which a given code
+    /// is set. It also carries a number of remaining attempts
+    /// before the door is permanently disabled.
+    Locked { code: Code, attempts: Attempts },
+
+    /// This state represents a disabled door. The police will
+    /// need to unlock it manually!
+    Disabled,
+}
+
+#[derive(Debug)]
+pub enum DoorEvent {
+    Open,
+    Close,
+    Lock(Code),
+    Unlock(Code),
+}
+
+#[derive(Debug, PartialEq)]
+pub enum DoorAction {
+    NotifyIRC(String),
+    CallThePolice,
+}
+
+impl FSM for DoorState {
+    type Event = DoorEvent;
+    type Action = DoorAction;
+
+    fn handle(self, event: DoorEvent) -> (Self, Vec<DoorAction>) {
+        match (self, event) {
+            (DoorState::Opened, DoorEvent::Close) => return (DoorState::Closed, vec![]),
+
+            (DoorState::Closed, DoorEvent::Open) => return (DoorState::Opened, vec![]),
+
+            (DoorState::Closed, DoorEvent::Lock(code)) => {
+                return (DoorState::Locked { code, attempts: 3 }, vec![])
+            }
+
+            (DoorState::Locked { code, attempts }, DoorEvent::Unlock(unlock_code)) => {
+                if code == unlock_code {
+                    return (DoorState::Closed, vec![]);
+                }
+
+                if attempts == 1 {
+                    return (DoorState::Disabled, vec![DoorAction::CallThePolice]);
+                }
+
+                return (
+                    DoorState::Locked {
+                        code,
+                        attempts: attempts - 1,
+                    },
+                    vec![DoorAction::NotifyIRC("invalid code entered".into())],
+                );
+            }
+
+            (current, _) => (current, vec![]),
+        }
+    }
+
+    fn enter(&self) -> Vec<DoorAction> {
+        let msg = match self {
+            DoorState::Opened => "door was opened",
+            DoorState::Closed => "door was closed",
+            DoorState::Locked { .. } => "door was locked",
+            DoorState::Disabled => "door was disabled",
+        };
+
+        vec![DoorAction::NotifyIRC(msg.into())]
+    }
+
+    fn act(action: DoorAction) -> Vec<DoorEvent> {
+        match action {
+            DoorAction::NotifyIRC(msg) => {
+                // TODO: write to file in example
+                println!("IRC: {}", msg);
+                vec![]
+            }
+
+            DoorAction::CallThePolice => {
+                // TODO: call the police
+                println!("The police was called! For real!");
+                vec![]
+            }
+        }
+    }
+}
+
+#[cfg(test)]
+mod tests {
+    use super::*;
+    use finito::advance;
+
+    fn test_fsm<S: FSM>(initial: S, events: Vec<S::Event>) -> (S, Vec<S::Action>) {
+        events.into_iter().fold((initial, vec![]), |(state, mut actions), event| {
+            let (new_state, mut new_actions) = advance(state, event);
+            actions.append(&mut new_actions);
+            (new_state, actions)
+        })
+    }
+
+    #[test]
+    fn test_door() {
+        let initial = DoorState::Opened;
+        let events = vec![
+            DoorEvent::Close,
+            DoorEvent::Open,
+            DoorEvent::Close,
+            DoorEvent::Lock(1234),
+            DoorEvent::Unlock(1234),
+            DoorEvent::Lock(4567),
+            DoorEvent::Unlock(1234),
+        ];
+        let (final_state, actions) = test_fsm(initial, events);
+
+        assert_eq!(final_state, DoorState::Locked { code: 4567, attempts: 2 });
+        assert_eq!(actions, vec![
+            DoorAction::NotifyIRC("door was closed".into()),
+            DoorAction::NotifyIRC("door was opened".into()),
+            DoorAction::NotifyIRC("door was closed".into()),
+            DoorAction::NotifyIRC("door was locked".into()),
+            DoorAction::NotifyIRC("door was closed".into()),
+            DoorAction::NotifyIRC("door was locked".into()),
+            DoorAction::NotifyIRC("invalid code entered".into()),
+        ]);
+    }
+}