Skip to content

Instantly share code, notes, and snippets.

@txus
Created February 20, 2017 17:07
Show Gist options
  • Save txus/58f2824e4a98f44dbb8d674565303270 to your computer and use it in GitHub Desktop.
Save txus/58f2824e4a98f44dbb8d674565303270 to your computer and use it in GitHub Desktop.
Compile-time verification of session-type-like programs in Clojure
(require '[clojure.string :as str])
(defn parse [s]
(->> s
str/split-lines
(map (fn [line]
(let [[action source destination alternative]
(str/split (str/trim line) #" +")]
[action
{:source source
:destination destination
:alternative alternative}])))
(into {})))
(defmacro session [protocol-filename [initial-state final-state] & actions]
(let [protocol (parse (slurp (io/resource protocol-filename)))
valid-actions (set (keys protocol))
valid-states (set (mapcat (juxt :source :destination) (vals protocol)))]
(cond
(not (valid-states initial-state))
(throw (ex-info (str "Invalid initial state " initial-state)
{:valid-states valid-states}))
(not (valid-states final-state))
(throw (ex-info (str "Invalid final state " final-state)
{:valid-states valid-states}))
:else
(loop [state initial-state
[action & nxt] actions]
(if-not (valid-actions action)
(throw (ex-info (str "Invalid action " action)
{:valid-actions valid-actions}))
(let [{:keys [source destination]} (get protocol action)]
(cond
(not= source state)
(throw (ex-info (str "Action " action " is not valid in state " state) {}))
(seq nxt)
(recur destination nxt)
(not (= destination final-state))
(throw (ex-info (str "Expected final state " final-state ", got " destination) {}))
:else ; we're done! program is correct
(into [] actions))))))))
;; At macro-expansion time, this session will be verified against the protocol
;; described in "vending-machine.txt", starting in state "waiting" and proving
;; that the session always ends in state "vended".
(session "vending-machine.txt" ["waiting" "vended"]
;; "hack" -> Won't compile as it's not a legal action described in vending-machine.txt
"pay"
"return"
;; "vend" -> Won't compile as it's not a legal action *in this state*.
"pay"
"select"
"vend") ;; if you forgot "vend", it wouldn't compile either, as the final state of this session *must* be "vended"
pay waiting paid broken
return paid waiting broken
select paid selected broken
vend selected vended broken
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment