Liking cljdoc? Tell your friends :D

salt

"S-expressions for Actions with Logic Temporal"

An experimental tool to convert a subset of Clojure into TLA+. Although it is experimental it has been used to produce real, useful TLA+ specifications.

You might want to use this if you know Clojure and are writing TLA+ specifications. Specifically it provides:

  • an interactive REPL based authoring experience
  • facilities for automated unit tests of invariants and actions
  • automatic formatting of resulting TLA+ code
  • a mapping from TLA+ to Clojure concepts that may facilitate learning TLA+

To use it:

  1. Write a specification using salt.
  2. Evaluate the salt specification in the REPL as part of the development process.
  3. Once it is ready to be run in the TLA+ Toolbox, invoke the salt transpiler to emit TLA+ code.
  4. Run the resulting TLA+ specification in the TLA+ Toolbox to assess the temporal properties of the specification.

In your salt specification file require the salt language as:

(:require [salt.lang :refer :all])

From the Clojure REPL, transpile a salt source file into a TLA+ file using an incantation like this:

(spit <tla+-filename> (salt.transpiler/transpile-text (slurp <salt-filename>)))

Alternatively, the salt transpiler can be invoked from the command line:

lein uberjar

java -cp target/salt-0.0.1-standalone.jar clojure.main -m salt.main <salt-filename>

Table of Contents

Language Identifiers

The following identifiers are reserved for the salt language.

Identifiers from the Clojure language:

and comment cond conj contains? count def defn difference expt first fn if intersection into let ns not or require select str subset? superset? union

Identifiers from the Clojure language, whose semantics were modified to match TLA+:

every?* get* map* mod* range* rest*

Identifiers that were added specifically to support transpiling, they are neither part of Clojure nor TLA+:

always- CHANGED- defm- eventually- fm- leads-to- line- maps- subset-proper? superset-proper? VARS-

Identifiers from the TLA+ language:

== => <=> = > < >= <= + - * A ASSUME Cardinality CHOOSE CONSTANT div DOMAIN E EXCEPT Nat not= UNCHANGED UNION SelectSeq Seq SF SubSeq SUBSET VARIABLE WF X

Clojure to TLA Concepts

ClojureClojure exampleTLA+TLA+ example
set#{1 2}set{1, 2}
vector[1 2]tuple<<1, 2>>
map{1 10 2 20}function(1 :> 10 @@ 2 :> 20)
map{:a 10 :b 20}record[a \|-> 100, b \|-> 200]
function(defn Add [x y] (+ 1 2))operatorAdd( x, y ) == x + y
lambda(fn [x] (> x 2))lambda in SelectSeqLAMBDA x: (x > 2)
lambda#(* 2 %)lambda in Except@ * 2

Technically a TLA+ operator is better understood as a Clojure macro rather than a Clojure function. However, since Clojure functions are easier to write and higher order functions are very natural to write, the decision was taken to model TLA+ operators as Clojure functions. This does not cause issues because the functions are never applied without having all of the variables bound. Whether they are applied or structurally substituted at that point does not matter.

In a way TLA+ functions might be usefully considered as Clojure functions. But, of course Clojure maps can be used as Clojure functions.

Standard Modules

Portions of some of the TLA+ standard modules have been implemented. They can be referenced from a salt file as follows:

  (:require [salt.lang :refer :all]
            [tlaplus.FiniteSets :refer :all]
            [tlaplus.Integers :refer :all]
            [tlaplus.Naturals :refer :all]
            [tlaplus.Sequences :refer :all])

The salt transpiler will detect these values in the salt source file and produce the corresponding EXTENDS statement in the TLA+ output.

Docs

Primitives

Integers are represented the same in salt and TLA+:

salttla+
code11
result11

Strings are sequences of characters

salttla+
code"hello""hello"
result"hello""hello

Symbols are identifiers. For example "x" is a symbol in the following:

salttla+
code(let [x 1] x)LET x == 1
IN x
result11

Boolean literals:

salttla+
codetrueTRUE
resulttrueTRUE
salttla+
codefalseFALSE
resultfalseFALSE

Code Structure

Let statements are used to establish bindings between symbols and values:

salttla+
code(let [x 1 y 2] (+ x y))LET x == 1
y == 2
IN x + y
result33

Conditional statements are expressed as:

salttla+
code(if true 100 0)IF TRUE
THEN 100
ELSE 0
result100100

To perform many checks on a value use 'cond'. NOTE: cond only allows one condition to be applied, whereas with TLA+ all matching conditions are possible results.

salttla+
code(let
[x 3]
(cond (= x 1) true (= x 2) true (= x 3) 7 :default false))
LET x == 3
IN CASE
(x = 1) -> TRUE
[] (x = 2) -> TRUE
[] (x = 3) -> 7
[] OTHER -> FALSE
result77

Arithmetic

Arithmetic on integers:

salttla+
code(+ 1 2)1 + 2
result33

Subtraction

salttla+
code(- 3 2)3 - 2
result11

Multiplication

salttla+
code(* 3 2)3 * 2
result66

Integer division

salttla+
code(div 10 2)10 \\div 2
result55

Integer division results are truncated

salttla+
code(div 9 2)9 \\div 2
result44

Compute the modulus of two numbers.

salttla+
code(mod* 10 3)10 % 3
result11

Compute exponentiation

salttla+
code(expt 2 3)2^3
result88

Refer to the set of natural numbers. The clojure version of this uses a very small set of natural numbers by default. NOTE: Nat is invoked as a function in clojure so that the upper limit can be dynamically bound, if necessary for testing.

salttla+
code(contains? (Nat) 2)2 \\in Nat
resulttrueTRUE

Logic

Use standard logic operators

salttla+
code(and true false)/\ TRUE
/\ FALSE
resultfalseFALSE

The 'or' operator

salttla+
code(or true false)\/ TRUE
\/ FALSE
resulttrueTRUE

Specify that if x is true then y must be true as well

salttla+
code(=> true false)TRUE => FALSE
resultfalseFALSE

Use the TLA+ <=> operator

salttla+
code(<=> true false)TRUE <=> FALSE
resultfalseFALSE

Check for equality

salttla+
code(= 5 5)5 = 5
resulttrueTRUE

Equality works on complex types

salttla+
code(= #{"a" "b"} #{"a" "b"}){ "a", "b" } = { "a", "b" }
resulttrueTRUE

Check for two items not being equal to each other

salttla+
code(not= 1 2)1 # 2
resulttrueTRUE

Use the standard inequality operators:

salttla+
code[(< 1 2) (<= 1 1) (> 2 1) (>= 2 2)]<< (1 < 2), (1 <= 1), (2 > 1), (2 >= 2) >>
result[true true true true]<<TRUE, TRUE, TRUE, TRUE>>

Specify something is true for some item in a set.

salttla+
code(E [x #{1 3 2}] true)\\E x \\in { 1, 3, 2 } :
TRUE
resulttrueTRUE

Specify something is true for all items in a set

salttla+
code(A [x #{1 3 2}] (> x 2))\\A x \\in { 1, 3, 2 } :
x > 2
resultfalseFALSE

Specs

Start a spec with a standard namespace declaration.

salttla+
code(ns
Buffer
(:require
[salt.lang :refer :all]
[tlaplus.Naturals :refer :all]
[tlaplus.Sequences :refer :all]))
---------------------------- MODULE Buffer ----------------------------
EXTENDS Naturals, Sequences

result

Define the CONSTANTS, which serve as a sort of 'input' to the specification and define the scope of the model.

salttla+
code(CONSTANT Clients Servers Data)CONSTANT Clients, Servers, Data

result

Make assertions about constants

salttla+
code(ASSUME (and (subset? Clients Servers) (< Limit 100)))ASSUME
/\ Clients \\subseteq Servers
/\ Limit < 100

result TRUE

Define the variables that make up the state:

salttla+
code(VARIABLE messages leaders)VARIABLE messages, leaders

result

Reference the variables via the convenience identifier VARS-

salttla+
codeVARS-<< messages, leaders >>
result[messages leaders]<< messages, leaders >>

Specify the initial state predicate.

salttla+
code(and (= messages []) (= leaders #{}))/\ messages = << >>
/\ leaders = {}
result

In action predicates reference primed variable symbols.

salttla+
code(and (= messages' []) (= leaders' #{}))/\ messages' = << >>
/\ leaders' = {}
result

Indicate variables that are not changed

salttla+
code(UNCHANGED [messages leaders])UNCHANGED << messages, leaders >>
resulttrue

As a departure from TLA+, just the changed variables can be indicated instead. This implies that other variables are unchanged.

salttla+
code(CHANGED- [leaders])UNCHANGED << messages >>
resulttrue

Include horizontal separator lines in the spec to delimit sections.

salttla+
code(line-)--------------------------------------------------------------------------------
result

Include comments in the spec.

salttla+
code(comment "this is a single line comment")
\\* this is a single line comment
result

Comments can be multi-line

salttla+
code(comment "this is a\\nmulti line comment")
(*
this is a
multi line comment
*)
result

Temporal Logic

Say something is always true

salttla+
code(always- (Next) vars)[][Next]_vars
result

Say something is eventually true

salttla+
code(eventually- (Done))<>Done
result

Say that something being true leads to something else being true

salttla+
code(leads-to- P Q)P ~> Q
result

Specify weak fairness

salttla+
code(WF vars (Next))WF_vars(Next)
result

Specify strong fairness

salttla+
code(SF vars (Next))SF_vars(Next)
result

Sets

Set literals are defined as:

salttla+
code#{1 3 2}{ 1, 3, 2 }
result#{1 3 2}{1, 2, 3}

A sequence of values is defined with range* Note that range* produces a set and is inclusive of the final value to match TLA+ semantics.

salttla+
code(range* 2 5)2..5
result#{4 3 2 5}{2 3 4 5}

Standard set operations come from the clojure.set namespace

salttla+
code[(union #{1 2} #{3 2})
(difference #{1 2} #{3 2})
(intersection #{1 2} #{3 2})]
<< ({ 1, 2 } \\union { 3, 2 }), ({ 1, 2 } \\ { 3, 2 }), ({ 1, 2 } \\intersect {
3, 2 }) >>
result[#{1 3 2} #{1} #{2}]<<{1, 2, 3}, {1}, {2}>>

Collapse many sets into one with UNION

salttla+
code(UNION #{#{4 3} #{3 5} #{1 2}})UNION { { 4, 3 }, { 3, 5 }, { 1, 2 } }
result#{1 4 3 2 5}{1, 2, 3, 4, 5}

The cartesian product of two sets is computed by the 'X' operator

salttla+
code(X #{1 3 2} #{"a" "b"}){ 1, 3, 2 } \\X { "a", "b" }
result#{[2 "b"] [3 "a"] [2 "a"] [1 "a"] [1 "b"] [3 "b"]}{<<1, "a">>, <<1, "b">>, <<2, "a">>, <<2, "b">>, <<3, "a">>, <<3, "b">>}

Check if a set is a subset of another

salttla+
code(subset? #{1 3} #{1 4 3 2}){ 1, 3 } \\subseteq { 1, 4, 3, 2 }
resulttrueTRUE

Define all of the sets that can be made from a set of values.

salttla+
code(SUBSET #{1 3 2})SUBSET { 1, 3, 2 }
result#{#{} #{3} #{2} #{1} #{1 3 2} #{1 3} #{1 2} #{3 2}}{{}, {1}, {2}, {3}, {1, 2}, {1, 3}, {2, 3}, {1, 2, 3}}

Check if a item is contained in a set.

salttla+
code(contains? #{1 3 2} 2)2 \\in { 1, 3, 2 }
resulttrueTRUE

Filter the values in a set to be those matching a predicate

salttla+
code(select (fn [x] (> x 10)) #{15 5}){ x \\in { 15, 5 } :
x > 10 }
result#{15}{15}

Apply a function to all elements of a set

salttla+
code(map* (fn [x] (+ 1 x)) #{1 2}){ 1 + x :
x \\in { 1, 2 } }
result#{3 2}{2, 3}

Compute the size of a set

salttla+
code(Cardinality #{20 10})Cardinality( { 20, 10 } )
result22

Use the TLA+ CHOOSE operator.

salttla+
code(CHOOSE [x #{1 3 2}] (>= x 3))CHOOSE x \\in { 1, 3, 2 } : (x >= 3)
result33

Vectors

Clojure vector literals are TLA+ tuples:

salttla+
code[1 "a"]<< 1, "a" >>
result[1 "a"]<<1, "a">>

Extract the first item from a vector:

salttla+
code(first [1 "a"])Head(<< 1, "a" >>)
result11

Extract a value by index. NOTE: the index starts with 1.

salttla+
code(get* [10 20 30] 2)<< 10, 20, 30 >>[2]
result2020

Produce a new vector containing all but the first item:

salttla+
code(rest* [1 "a"])Tail(<< 1, "a" >>)
result["a"]<<"a">>

Produce a new vector with one element changed:

salttla+
code(EXCEPT [10 20 30] [2] 200)[<< 10, 20, 30 >> EXCEPT ![2] = 200]
result[10 200 30]<<10, 200, 30>>

Combine the contents of two vectors into a new vector

salttla+
code(into [1 "a"] [2])<< 1, "a" >> \\o << 2 >>
result[1 "a" 2]<<1, "a", 2>>

Combine two strings, which TLA+ treats as tuples:

salttla+
code(str "hel" "lo")"hel" \\o "lo"
result"hello""hello"

Compute the length of a vector or string

salttla+
code[(count [1 "a"]) (count "hello")]<< Len( << 1, "a" >> ), Len( "hello" ) >>
result[2 5]<<2, 5>>

Extract a subsequence by index from a vector

salttla+
code(SubSeq [10 20 30 40] 2 3)SubSeq(<< 10, 20, 30, 40 >>, 2, 3)
result[20 30]<<20, 30>>

Filter out the values in a vector

salttla+
code(SelectSeq (fn [x] (> x 2)) [1 2 3 4])SelectSeq(<< 1, 2, 3, 4 >>, LAMBDA x: (x > 2))
result[3 4]<<3, 4>>

Add an item to the end of a vector

salttla+
code(conj [1 2] 3)Append(<< 1, 2 >>, 3)
result[1 2 3]<<1, 2, 3>>

Generate all possible vectors from a set of values.

salttla+
code(Seq #{100 200})Seq( { 100, 200 } )
result#{[100] [100 200 200] [200 100 200] [200 200] [200 100 100]
[100 200] [] [100 100] [100 200 100] [200 200 200]
[100 100 100] [200 200 100] [200] [200 100] [100 100 200]}
Seq({100, 200})

The idiom of calling 'every?*' with a set as a predicate translates into a corresponding TLA+ idiom for checking a TLA+ tuple.

salttla+
code(every?* #{1 4 3 2 5} [1 3])<< 1, 3 >> \\in (Seq( { 1, 4, 3, 2, 5 } ))
resulttrueTRUE

Compute all of the indexes present in a vector

salttla+
code(DOMAIN [10 20 30])DOMAIN << 10, 20, 30 >>
result#{1 3 2}1..3

Convert a vector to a set

salttla+
code(map* (fn [i] (get* [10 20 30] i)) (DOMAIN [10 20 30])){ << 10, 20, 30 >>[i] :
i \\in DOMAIN << 10, 20, 30 >> }
result#{20 30 10}{10, 20, 30}

Maps

Use 'maps-' to generate all possible maps for a set of possible keys and a set of possible values.

salttla+
code(maps- #{20 10} #{100 200})[{ 20, 10 } -> { 100, 200 }]
result#{{20 100, 10 200} {20 200, 10 100} {20 100, 10 100}
{20 200, 10 200}}
{ (10 :> 100 @@ 20 :> 100),
(10 :> 100 @@ 20 :> 200),
(10 :> 200 @@ 20 :> 100),
(10 :> 200 @@ 20 :> 200) }

Define a map via 'fm-'.

salttla+
code(fm- [a #{20 10}] 30)[a \\in { 20, 10 } \|-> 30]
result{20 30, 10 30}(10 :> 30 @@ 20 :> 30)

Use 'defm-' to define a map like with 'fm-', but assign the result a name.

salttla+
code(defm- MyMaps [a #{1 2}] (* a 10))MyMaps == [a \\in { 1, 2 } \|-> (a * 10)]

result

Extract a value by key.

salttla+
code(get* (fm- [a #{20 10}] (* a 10)) 10)[a \\in { 20, 10 } \|-> (a * 10)][10]
result100100

Maps As Records

Map literals whose keys are keywords become TLA+ records, which are a special type of a TLA+ function

salttla+
code{:a 100, :b 200}[a \|-> 100,
b \|-> 200]
result{:a 100, :b 200}[a \|-> 100, b \|-> 200]

Access the values in a TLA+ record.

salttla+
code(get* {:a 100, :b 200} :b)[a \|-> 100,
b \|-> 200]["b"]
result200200

Use 'DOMAIN' on maps to obtain the keys.

salttla+
code(DOMAIN {:a 100, :b 200})DOMAIN [a \|-> 100,
b \|-> 200]
result#{:b :a}{"a", "b"}

Produce a new TLA+ record with modified values, like assoc-in

salttla+
code(EXCEPT {:a 1, :b 2, :c 3} [:b] 20)[[a \|-> 1,
b \|-> 2,
c \|-> 3] EXCEPT !["b"] = 20]
result{:a 1, :b 20, :c 3}[a \|-> 1, b \|-> 20, c \|-> 3]

Produce a new nested TLA+ record with modified values, like assoc-in

salttla+
code(EXCEPT
{:a {:x 1, :y 10}, :b {:x 2, :y 20}, :c {:x 3, :y 30}}
[:b :x]
200)
[[a \|-> [x \|-> 1,
y \|-> 10],
b \|-> [x \|-> 2,
y \|-> 20],
c \|-> [x \|-> 3,
y \|-> 30]] EXCEPT !["b"]["x"] = 200]
result{:a {:x 1, :y 10}, :b {:x 200, :y 20}, :c {:x 3, :y 30}}[ a \|-> [x \|-> 1, y \|-> 10],
b \|-> [x \|-> 200, y \|-> 20],
c \|-> [x \|-> 3, y \|-> 30] ]

Produce a new TLA+ record with new values computed by lambda function, like update-in

salttla+
code(EXCEPT {:a {:x 1 :y 10}
:b {:x 2 :y 20}
:c {:x 3 :y 30}}
[:b :x]
#(* % 2))
[[a \|-> [x \|-> 1,
y \|-> 10],
b \|-> [x \|-> 2,
y \|-> 20],
c \|-> [x \|-> 3,
y \|-> 30]] EXCEPT !["b"]["x"] = @ * 2]
result{:a {:x 1, :y 10}, :b {:x 4, :y 20}, :c {:x 3, :y 30}}[[
a \|-> [
x \|-> 1,
y \|-> 10],
b \|-> [
x \|-> 2,
y \|-> 20],
c \|-> [
x \|-> 3,
y \|-> 30]] EXCEPT !["b"]["x"] = @ * 2]

Produce a new TLA+ record with new values and with lambdas, like combining assoc-in and update-in

salttla+
code(EXCEPT {:a {:x 1 :y 10}
:b {:x 2 :y 20}
:c {:x 3 :y 30}}
[:b :x] #(* % 2)
[:a] "new")
[[a \|-> [x \|-> 1,
y \|-> 10],
b \|-> [x \|-> 2,
y \|-> 20],
c \|-> [x \|-> 3,
y \|-> 30]] EXCEPT
!["b"]["x"] = @ * 2,
!["a"] = "new"]
result{:a "new", :b {:x 4, :y 20}, :c {:x 3, :y 30}}[a \|-> "new", b \|-> [x \|-> 4, y \|-> 20], c \|-> [x \|-> 3, y \|-> 30]]

Use 'maps-' to generate all possible TLA+ records for pairs of keys and value sets

salttla+
code(maps- [:name #{"bob" "sue"} :age #{20 10}])[name : { "bob", "sue" },
age : { 20, 10 }]
result#{{:name "sue", :age 20} {:name "bob", :age 20}
{:name "sue", :age 10} {:name "bob", :age 10}}
{[name \|-> "bob", age \|-> 10], [name \|-> "bob", age \|-> 20], [name \|-> "sue", age \|-> 10], [name \|-> "sue", age \|-> 20]}

Define a map via 'fm-', will auto-convert to a TLA+ record.

salttla+
code(fm- [a #{"a" "b"}] 30)[a \\in { "a", "b" } \|-> 30]
result{"a" 30, "b" 30}[a \|-> 30, b \|-> 30]

Maps As Tuples

Maps whose keys start with 1 and proceed in increments of 1 are treated as TLA+ tuples.

salttla+
code{1 100, 2 200}<< 100, 200 >>
result{1 100, 2 200}<<100, 200>>

Use 'DOMAIN' on maps that correspond to TLA+ tuples.

salttla+
code(DOMAIN {1 100, 2 200})DOMAIN << 100, 200 >>
result#{1 2}1..2

Extract a value from a TLA+ tuple by index. NOTE: the index starts with 1.

salttla+
code(get* {1 100, 2 200} 1)<< 100, 200 >>[1]
result100100

Define a map via 'fm-', will auto-convert to a TLA+ tuple

salttla+
code(fm- [a #{1 2}] (* a 10))[a \\in { 1, 2 } \|-> (a * 10)]
result{1 10, 2 20}<<10, 20>>

Functions

Define a function using fn. Depending on the context it will be transpiled to different forms.

salttla+
code[(SelectSeq (fn [x] (> x 2)) [1 2 3 4])
(map* (fn [x] (+ 1 x)) #{1 2})]
<< (SelectSeq(<< 1, 2, 3, 4 >>, LAMBDA x: (x > 2))), (
{ 1 + x :
x \\in { 1, 2 } }) >>
result[[3 4] #{3 2}]<<<<3, 4>>, {2, 3}>>

Define a new TLA+ operator with defn:

salttla+
code(defn Add [x y] (+ x y))Add( x, y ) ==
x + y

result

Use a docstring with defn

salttla+
code(defn Add "docstring" [x y] (+ x y))
\\* docstring
Add( x, y ) ==
x + y

result

Invoke a function as normal:

salttla+
code(Add 1 2)Add(1, 2)
result33

Define a recursive function:

salttla+
code(defn Add [x r] (if (> x 5) (Add (- x 1) (+ r 1)) r))RECURSIVE Add(_, _)

Add( x, r ) ==
IF (x > 5)
THEN Add((x - 1), (r + 1))
ELSE r

result

Define a higher-order function:

salttla+
code(defn Work [f a b] (f a b))Work( f(_, _), a, b ) ==
f(a, b)

result

Define functions that take no arguments as usual:

salttla+
code(defn Work [] (union a b))Work == a \\union b

result

Define functions that take no arguments as usual (with docstring):

salttla+
code(defn Work "docstring" [] (union a b))
\\* docstring
Work == a \\union b

result

Invoke a function with no arguments.

salttla+
code(Work)Work
result

Define TLA+ operators that only rely on constants with def:

salttla+
code(def Work (union A B))Work == A \\union B

result

Reference a TLA+ operator that only relies on constants

salttla+
codeWorkWork
result

Example Salt

The following is a full sample salt file:

(comment "example spec ported from
https://github.com/tlaplus/Examples/blob/master/specifications/transaction_commit/TwoPhase.tla")

(ns TwoPhase
  (:require [salt.lang :refer :all]))

(CONSTANT RM)

(VARIABLE
 rmState
 tmState
 tmPrepared
 msgs)

(defn Message [] (union (maps- [:type #{"Prepared"}
                                :rm RM])
                        (maps- [:type #{"Commit" "Abort"}])))

(defn TPTypeOk []
  (and (contains? (maps- RM #{"working" "prepared" "committed" "aborted"}) rmState)
       (contains? #{"init" "committed" "aborted"} tmState)
       (subset? tmPrepared RM)
       (subset? msgs Message)))

(defn TPInit []
  (and (= rmState (fm- [rm RM]
                       "working"))
       (= tmState "init")
       (= tmPrepared #{})
       (= msgs #{})))

(defn TMRcvPrepared [rm]
  (and (= tmState "init")
       (contains? msgs {:type "Prepared"
                        :rm rm})
       (= tmPrepared' (union tmPrepared #{rm}))
       (CHANGED- [tmPrepared])))

(defn TMCommit []
  (and (= tmState "init")
       (= tmPrepared RM)
       (= tmState' "committed")
       (= msgs' (union msgs #{{:type "Commit"}}))
       (CHANGED- [tmState msgs])))

(defn TMAbort []
  (and (= tmState "init")
       (= tmState' "aborted")
       (= msgs' (union msgs #{{:type "Abort"}}))
       (CHANGED- [tmState msgs])))

(defn RMPrepare [rm]
  (and (= (get* rmState rm) "working")
       (= rmState' (EXCEPT rmState [rm] "prepared"))
       (= msgs' (union msgs #{{:type "Prepared"
                               :rm rm}}))
       (CHANGED- [rmState msgs])))

(defn RMChooseToAbort [rm]
  (and (= (get* rmState rm) "working")
       (= rmState' (EXCEPT rmState [rm] "aborted"))
       (CHANGED- [rmState])))

(defn RMRcvCommitMsg [rm]
  (and (contains? msgs {:type "Commit"})
       (= rmState' (EXCEPT rmState [rm] "committed"))
       (CHANGED- [rmState])))

(defn RMRcvAbortMsg [rm]
  (and (contains? msgs {:type "Abort"})
       (= rmState' (EXCEPT rmState [rm] "aborted"))
       (CHANGED- [rmState])))

(defn TPNext []
  (or (TMCommit)
      (TMAbort)
      (E [rm RM]
         (or (TMRcvPrepared rm)
             (RMPrepare rm)
             (RMChooseToAbort rm)
             (RMRcvCommitMsg rm)
             (RMRcvAbortMsg rm)))))

(defn TPSpec []
  (and (TPInit)
       (always- (TPNext) [rmState tmState tmPrepared msgs])))

Example TLA

The following is a full sample TLA+ output produced by the salt file above:

---------------------------- MODULE TwoPhase ----------------------------

(*
example spec ported from
https://github.com/tlaplus/Examples/blob/master/specifications/transaction_commit/TwoPhase.tla
*)
CONSTANT RM

VARIABLE rmState, tmState, tmPrepared, msgs

Message == [type : {"Prepared"},
            rm : RM] \union [type : { "Commit", "Abort" }]

TPTypeOk == 
    /\  rmState \in [RM -> { "committed", "prepared", "aborted", "working" }]
    /\  tmState \in { "committed", "aborted", "init" }
    /\  tmPrepared \subseteq RM
    /\  msgs \subseteq Message

TPInit == 
    /\  rmState = [rm \in RM |-> "working"]
    /\  tmState = "init"
    /\  tmPrepared = {}
    /\  msgs = {}

TMRcvPrepared( rm ) ==
    /\  tmState = "init"
    /\  [type |-> "Prepared",
         rm |-> rm] \in msgs
    /\  tmPrepared' = tmPrepared \union {rm}
    /\  UNCHANGED << rmState, tmState, msgs >>

TMCommit == 
    /\  tmState = "init"
    /\  tmPrepared = RM
    /\  tmState' = "committed"
    /\  msgs' = msgs \union {[type |-> "Commit"]}
    /\  UNCHANGED << rmState, tmPrepared >>

TMAbort == 
    /\  tmState = "init"
    /\  tmState' = "aborted"
    /\  msgs' = msgs \union {[type |-> "Abort"]}
    /\  UNCHANGED << rmState, tmPrepared >>

RMPrepare( rm ) ==
    /\  rmState[rm] = "working"
    /\  rmState' = [rmState EXCEPT ![rm] = "prepared"]
    /\  msgs' = msgs \union {[type |-> "Prepared",
                              rm |-> rm]}
    /\  UNCHANGED << tmState, tmPrepared >>

RMChooseToAbort( rm ) ==
    /\  rmState[rm] = "working"
    /\  rmState' = [rmState EXCEPT ![rm] = "aborted"]
    /\  UNCHANGED << tmState, tmPrepared, msgs >>

RMRcvCommitMsg( rm ) ==
    /\  [type |-> "Commit"] \in msgs
    /\  rmState' = [rmState EXCEPT ![rm] = "committed"]
    /\  UNCHANGED << tmState, tmPrepared, msgs >>

RMRcvAbortMsg( rm ) ==
    /\  [type |-> "Abort"] \in msgs
    /\  rmState' = [rmState EXCEPT ![rm] = "aborted"]
    /\  UNCHANGED << tmState, tmPrepared, msgs >>

TPNext == 
    \/  TMCommit
    \/  TMAbort
    \/  \E rm \in RM :
            \/  TMRcvPrepared(rm)
            \/  RMPrepare(rm)
            \/  RMChooseToAbort(rm)
            \/  RMRcvCommitMsg(rm)
            \/  RMRcvAbortMsg(rm)

TPSpec == 
    /\  TPInit
    /\  [][TPNext]_<< rmState, tmState, tmPrepared, msgs >>


=============================================================================

Change Log

2019/08/19 version 0.0.2 Introduced symbolic evaluator that evaluates where possible and then simplifies for testing action predicates.

Copyright

Copyright (c) 2019, ViaSat Inc. All rights reserved.

Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met:

Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer.

Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution.

THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, PUNITIVE, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.

Can you improve this documentation? These fine people already did:
david-mcneil, dmcneil & Russell Mull
Edit on GitHub

cljdoc is a website building & hosting documentation for Clojure/Script libraries

× close