Skip to content

Commit 08d68e6

Browse files
committed
Update Tutorial
1 parent e3d4375 commit 08d68e6

File tree

8 files changed

+45
-37
lines changed

8 files changed

+45
-37
lines changed

README.md

Lines changed: 23 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -52,9 +52,8 @@ To follow along with this tutorial, make sure you have the following installed:
5252

5353
- Visual Studio Code, for editing the code and using the LiquidJava extension
5454
- [Java Extension Pack by Red Hat](vscode:extension/redhat.java), which provides Java support in VS Code
55-
- [LiquidJava Extension](vscode:extension/AlcidesFonseca.liquid-java), which provides the LiquidJava typechecker with real-time error reporting and syntax highlighting for the refinements.
56-
57-
> Don't forget to star [LiquidJava](https://github.com/liquid-java/liquidjava) and its [extension](https://github.com/liquid-java/vscode-liquidjava) on GitHub! ⭐
55+
- [LiquidJava Extension](vscode:extension/AlcidesFonseca.liquid-java), which automatically runs the LiquidJava verification and reports errors in the editor.
56+
- Also don't forget to star [LiquidJava](https://github.com/liquid-java/liquidjava) and its [extension](https://github.com/liquid-java/vscode-liquidjava) on GitHub! ⭐
5857

5958
---
6059

@@ -66,9 +65,9 @@ First of all, let's explore how basic refinements work in LiquidJava.
6665

6766
> Open [RefinementsExample.java](./src/main/java/com/tutorial/part1/RefinementsExample.java)
6867
69-
In the `main` method, you can find four variables, `positive`, `nonzero`, `percentage` and `direction`, with three of them containing comments with the refinements that should be used in each one. Notice that `_` can also be used as a placeholder for the variable name in the refinement expression, as shown in the refinement for the `percentage` variable.
68+
Here, you can find four methods, each one with a single local variable. The first three variables contain comments with the refinements that should be used in each one. Notice that `_` can also be used as a placeholder for the variable name in the refinement expression, as shown in the refinement for the `percentage` variable.
7069

71-
> Now, one by one, uncomment the `@Refinement` annotations and observe each error reported. Remember that only one can be shown at a time. Then, change each value to satisfy the corresponding refinement — change the value of `positive` to a positive integer, the value of `nonzero` to any non-zero integer, and the value of `percentage` to an integer between `0` and `100`.
70+
> Now, one by one, uncomment the `@Refinement` annotations and observe each error reported. Then, change each value to satisfy the corresponding refinement — change the value of `positive` to a positive integer, the value of `nonzero` to any non-zero integer, and the value of `percentage` to an integer between `0` and `100`.
7271
7372
> Then, add the refinement for the `direction` variable, which should ensure its value is always either `-1` or `1` (you should use the `||` operator for this). After that, change its value to satisfy the refinement.
7473
@@ -116,13 +115,17 @@ Let's explore how to use **state refinements** to specify and verify properties
116115

117116
> Open [LightBulb.java](./src/main/java/com/tutorial/part2/LightBulb.java).
118117
119-
Here, we specify that this object can only be in two states: `on` or `off`. Then, in the constructor, we specify that the initial state is `off`, through the `@StateRefinement` annotation. This annotation allows us to specify in which state the object should be before the method is called (`from`), and in which state it will be after the method execution (`to`). In the constructor, since it's the first method to be called, we can only specify the `to` state.
118+
Here, we specify that, at any moment, this object can only be in two states: `on` or `off`. Then, in the constructor, we specify that the initial state is `off`, through the `@StateRefinement` annotation. This annotation allows us to specify in which state the object should be before the method is called (`from`), and in which state it will be after the method execution (`to`). In the constructor, since it's the first method to be called, we can only specify the `to` transition.
119+
120+
This object has two methods, `turnOn` and `turnOff`. From the state refinements, we can see that the method `turnOn` can only be called when the object is in state `off` transiting to state `on`. Similarly, the method `turnOff` can only be called when the object is in state `on`, transiting to state `off`. This means that we cannot call the same method twice in a row, since it would violate the protocol established by the state refinements.
120121

121-
This object has two methods, `turnOn` and `turnOff`. From the state refinements, we can see that the method `turnOn` can only be called when the object is in state `off` transiting to state `on`. Similarly, the method `turnOff` can only be called when the object is in state `on`, transiting to state `off`. This means that we cannot call the same method twice in a row, since it would violate the protocol established by the state refinements. The following DFA illustrates this:
122+
Notice that `on` and `off` are instance methods. By default, they use the `this` receiver, so `on()`, `this.on()`, and `on(this)` are all equivalent.
123+
124+
The following DFA illustrates this:
122125

123126
![Light Bulb DFA](./docs/images/light_bulb_dfa.png)
124127

125-
> Uncomment line 22 to observe the error.
128+
> Uncomment **line 22** to observe the error.
126129
127130
#### Exercise
128131

@@ -136,10 +139,10 @@ If you get stuck, here are some **hints**:
136139

137140
- Follow the diagram carefully
138141
- For each edge in the diagram, identify the method that causes that transition and the source and target states
139-
- If a method is allowed from multiple source states, use the `||` operator to combine them
140-
- Don't forget the `(this)` after each state name, since states are always associated with an object instance
142+
- If a method is allowed from multiple source states, you can either use the `||` operator to combine them
143+
- Don't forget the parentheses after each state name, since there are functions
141144

142-
With the correct implementation, LiquidJava will report an error in line 30, since we are trying to resume playback when the player is stopped.
145+
With the correct implementation, LiquidJava will report an error in **line 30**, since we are trying to resume playback when the player is stopped.
143146

144147
### 3. External Refinements
145148

@@ -153,7 +156,7 @@ Here, we refine the `Socket` class through state refinements, with the possible
153156

154157
> Open [SocketExample.java](./src/main/java/com/tutorial/part3/SocketExample.java).
155158
156-
Here, we see a simple usage of the `Socket` class. If you comment out the line 9 containing with the `bind` method call, LiquidJava will report an error in the `connect` method call, since it violates the state refinement specified for the `Socket` class! Notice that when using the `Socket` class, we don't need to deal with any refinement annotations, since they are already specified in the external refinement interface.
159+
Here, we see a simple usage of the `Socket` class. If you comment out the **line 9** containing with the `bind` method call, LiquidJava will report an error in the `connect` method call, since it violates the state refinement specified for the `Socket` class! Notice that when using the `Socket` class, we don't need to deal with any refinement annotations, since they are already specified in the external refinement interface.
157160

158161
#### Exercise
159162

@@ -165,44 +168,43 @@ We want to ensure that the `lock` method can only be called in the `unlocked` st
165168

166169
![ReentrantLock DFA](./docs/images/reentrant_lock_dfa.png)
167170

168-
With the correct implementation, LiquidJava will report an error in line 10 of [ReentrantLockExample.java](./src/main/java/com/tutorial/part3/exercise/ReentrantLockExample.java), since we are trying to unlock a lock that is not locked.
171+
With the correct implementation, LiquidJava will report an error in **line 10** of [ReentrantLockExample.java](./src/main/java/com/tutorial/part3/exercise/ReentrantLockExample.java), since we are trying to unlock a lock that is not locked.
169172

170173
### 4. Ghost Variables
171174

172-
Finally, LiquidJava also offers a way to model objects using **ghost variables** through the `@Ghost` annotation, which are used to track additional information about the program's state when states aren't enough. These can be, for instance, counters (integers) or flags (booleans), to model more complex protocols.
175+
Finally, LiquidJava also offers a way to model objects using **ghost variables** through the `@Ghost` annotation, which are used to track additional information about the program's state when typestates aren't enough. These can be, for instance, counters (integers) or flags (booleans), to model more complex protocols.
173176

174177
> Open [ArrayListRefinements.java](./src/main/java/com/tutorial/part4/ArrayListRefinements.java).
175178
176-
Here, we define the refinements for the `ArrayList` class, using a ghost variable `size` to keep track of the number of elements in the list. Using the `size` ghost variable in state refinements, we can prevent out-of-bounds access.
179+
Here, we define the refinements for the `java.util.ArrayList` class, using a ghost variable `size` to keep track of the number of elements in the list, to prevent out-of-bounds access.
177180

178-
In the constructor, we specify that after it is called, the ghost variable `size` will be equal to `0`. This is optional since its default value is already zero, but it helps us understand this example. Then, in the `add` method, we specify that it can be called in any state (since we don't specify a `from` state), and that after it is called, the `size` ghost variable will be incremented by one — the new size will be equal to the old size plus one (`old` is a special keyword that refers to the previous state of the object, so calling `size(old(this))` gets the value of `size` before the method was called). Finally, in the `get` method, we specify that the index parameter must be non-negative and less than the current size of the list, therefore preventing out-of-bounds errors.
181+
In the constructor, we specify that after it is called, the ghost variable `size` will be equal to `0`. This is optional since the default value for integers is already zero. Then, in the `add` method, we specify that it can be called in any state (since we don't specify a `from` state), and that after it is called, the `size` ghost variable will be incremented by one — the new size will be equal to the old size plus one (`old(this)` is a special function that refers to the previous state of the object, so calling `size(old(this))` gets the value of `size` before the method was called). Finally, in the `get` method, we specify that the index parameter must be between 0 and the size of the list, therefore preventing out-of-bounds errors.
179182

180183
> Open [ArrayListExample.java](./src/main/java/com/tutorial/part4/ArrayListExample.java).
181184
182-
Here, we can see a simple usage of the refined `ArrayList` class. If you uncomment line 11, LiquidJava will report an error, since we are trying to access an index that is out of bounds!
185+
Here, we can see a simple usage of the `ArrayList` class. If you uncomment **line 11**, LiquidJava will report an error, since we are trying to access an index that is out of bounds!
183186

184187
#### Exercise
185188

186-
Let's do the same but for the `Stack` class.
189+
Let's do the same but for the `java.util.Stack` class.
187190

188191
> Open [StackRefinements.java](./src/main/java/com/tutorial/part4/exercise/StackRefinements.java). Your task is to refine the `Stack` class by replacing the `"true"` refinements with the appropriate ones to ensure the correct behavior of the `push`, `pop` and `peek` methods, using the `count` ghost variable to keep track of the number of elements in the stack, and not allow incorrect uses of these methods — popping or peeking from an empty stack.
189192
190193
If you get stuck, here are some **hints**:
191194

192195
- You may find it useful to look at the previous example for reference
193196
- The predicates must be boolean expressions
194-
- You should use the `old` keyword to refer to the previous state of the object
197+
- You should use the `old(this)` function to refer to the previous state of the object
195198
- You should use the `count` ghost variable in all refinements
196199

197-
With the correct implementation, LiquidJava will report an error in line 11 of [StackExample.java](./src/main/java/com/tutorial/part4/exercise/StackExample.java), since we are trying to pop an element of the stack when it is empty.
200+
With the correct implementation, LiquidJava will report an error in **line 11** of [StackExample.java](./src/main/java/com/tutorial/part4/exercise/StackExample.java), since we are trying to pop an element of the stack when it is empty.
198201

199202
---
200203

201204
## Quiz
202205

203206
Test your knowledge about LiquidJava by taking a simple quiz available [here](https://liquid-java.github.io/liquidjava-tutorial/). Think you can get a perfect score?
204207

205-
206208
If you encounter any problems or have any questions, feel free to send an email to:
207209

208210
- [Ricardo Costa](mailto:rcosta.ms358@gmail.com)

src/main/java/com/tutorial/part1/RefinementsExample.java

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,16 +5,22 @@
55
@SuppressWarnings("unused")
66
public class RefinementsExample {
77

8-
public static void main(String[] args) {
8+
void example1() {
99
// @Refinement("positive > 0")
1010
int positive = -1;
11+
}
1112

13+
void example2() {
1214
// @Refinement("nonzero != 0")
1315
int nonzero = 0;
16+
}
1417

18+
void example3() {
1519
// @Refinement("0 <= _ && _ <= 100")
1620
int percentage = 200;
21+
}
1722

23+
void example4() {
1824
// TODO: add refinement to ensure value is either -1 or 1
1925
int direction = 0;
2026
}

src/main/java/com/tutorial/part2/LightBulb.java

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -6,13 +6,13 @@
66
@StateSet({"on", "off"})
77
public class LightBulb {
88

9-
@StateRefinement(to="off(this)")
9+
@StateRefinement(to="off()")
1010
public LightBulb() {}
1111

12-
@StateRefinement(from="off(this)", to="on(this)")
12+
@StateRefinement(from="off()", to="on()")
1313
public void turnOn() {}
1414

15-
@StateRefinement(from="on(this)", to="off(this)")
15+
@StateRefinement(from="on()", to="off()")
1616
public void turnOff() {}
1717

1818
public static void main(String[] args) {

src/main/java/com/tutorial/part2/exercise/MediaPlayer.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@
66
@StateSet({"stopped", "playing", "paused"})
77
public class MediaPlayer {
88

9-
@StateRefinement(to="stopped(this)")
9+
@StateRefinement(to="stopped()")
1010
public MediaPlayer() {}
1111

1212
@StateRefinement(from="true", to="true")

src/main/java/com/tutorial/part3/SocketRefinements.java

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -9,18 +9,18 @@
99
@StateSet({"unconnected", "bound", "connected", "closed"})
1010
public interface SocketRefinements {
1111

12-
@StateRefinement(to="unconnected(this)")
12+
@StateRefinement(to="unconnected()")
1313
public void Socket();
1414

15-
@StateRefinement(from="unconnected(this)", to="bound(this)")
15+
@StateRefinement(from="unconnected()", to="bound()")
1616
public void bind(SocketAddress add);
1717

18-
@StateRefinement(from="bound(this)", to="connected(this)")
18+
@StateRefinement(from="bound()", to="connected()")
1919
public void connect(SocketAddress add);
2020

21-
@StateRefinement(from="connected(this)")
21+
@StateRefinement(from="connected()")
2222
public void sendUrgentData(int n);
2323

24-
@StateRefinement(from="!closed(this)", to="closed(this)")
24+
@StateRefinement(from="!closed()", to="closed()")
2525
public void close();
2626
}

src/main/java/com/tutorial/part3/exercise/ReentrantLockRefinements.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@
88
@StateSet({"unlocked", "locked"})
99
public interface ReentrantLockRefinements {
1010

11-
@StateRefinement(to="unlocked(this)")
11+
@StateRefinement(to="unlocked()")
1212
public void ReentrantLock();
1313

1414
@StateRefinement(from="true", to="true")

src/main/java/com/tutorial/part4/ArrayListExample.java

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -5,8 +5,8 @@
55
public class ArrayListExample {
66

77
public static void main(String[] args) {
8-
ArrayList<Integer> list = new ArrayList<>();
9-
list.add(10);
8+
ArrayList<String> list = new ArrayList<>();
9+
list.add("hello");
1010
list.get(0);
1111
// list.get(1); // uncomment for error
1212
}

src/main/java/com/tutorial/part4/ArrayListRefinements.java

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -9,11 +9,11 @@
99
@Ghost("int size")
1010
public interface ArrayListRefinements<E> {
1111

12-
@StateRefinement(to="size(this) == 0")
12+
@StateRefinement(to="size() == 0")
1313
public void ArrayList();
1414

15-
@StateRefinement(to="size(this) == size(old(this)) + 1")
15+
@StateRefinement(to="size() == size(old(this)) + 1")
1616
public boolean add(E elem);
1717

18-
public E get(@Refinement("0 <= _ && _ < size(this)") int index);
18+
public E get(@Refinement("0 <= _ && _ < size()") int index);
1919
}

0 commit comments

Comments
 (0)