Logika Programs
As we study program logic, we will use Logika to verify a subset of programs that use the Scala language. Specifically, we will study verification of programs with the following features:
- Variables (booleans, ints, and sequences [which are like arrays/lists])
- Printing and user input
- Math operations
- Conditional operations
- If and if/else statements
- While loops
- Functions
The Scala programs we verify should be saved with a .sc extension. To show we are using Logika for verification, the first line of the file should be:
// #Sireum #Logika
Verifying Logika programs
There are two modes in Logika – manual and automatic. In chapters 8 and 9, we will use manual mode. In chapter 10, we will use automatic mode. You will designate the verification mode on the second line of the Scala file (just below // #Sireum #Logika
). To specify manual mode, you will write:
//@Logika: --manual --background save
And to specify automatic mode, you will delete the --manual
from above, like this:
//@Logika: --background save
Logika verification should run automatically as you edit these programs and their proofs. If a program is verified, you will see a purple checkmark in the lower right corner just as you did in propositional and predicate logic proofs. If there are syntax or logic errors, you will see them highlighted in red.
Sometimes, the Logika verification needs to be run manually. If you don’t see either red errors or a purple checkmark, right-click in the text area that contains the code and select “Logika Check (All in File)”.
Example programs
This section contains four sample Scala programs that highlight the different language features that we will work with.
Example 1: User input, printing, operations
This first example gets a number as input from the user, adds one to it, and prints the result:
// #Sireum #Logika
//@Logika: --manual --background save
import org.sireum._
var x: Z = Z.prompt("Enter a number: ")
x = x + 1
println("One more is ", x)
A few things to note:
- After the two comment lines to show we are using the Logika verification tool in manual mode, our program begins with an import statement:
import org.sireum._
- The
var
keyword stands for variable, which is something that can be changed. Logika also has aval
keyword, which creates a constant value that cannot be changed. - Lines do not end in semi-colons
- The
Z.prompt(...)
function prints a prompt and returns the integer (Z
) that was typed. The parameter to this function is the desired prompt. Alternately, theZ.read()
function gets and returns an integer from user input without taking a prompt. - The code
var x: Z
creates a variable calledx
of typeZ
– as with the user input functions, theZ
means integer
Example 2: If/else statements
Here is a Scala program that gets a number from the user, and uses an if/else statement to print whether the number is positive or negative/zero:
// #Sireum #Logika
//@Logika: --manual --background save
import org.sireum._
val num: Z = Z.prompt("Enter a number: ")
if (num > 0) {
println(num, " is positive")
} else {
println(num, " is negative (or zero)")
}
A couple of things to note here:
- The
else
statement MUST appear on the same line as the closing}
of the previous if-statement - As mentioned above, the
val
keyword in this program means thatnum
cannot be changed after being initialized
Example 3: While loops
Our next program uses a while loop to print the numbers from 10 down to 1:
// #Sireum #Logika
//@Logika: --manual --background save
import org.sireum._
var cur: Z = 10
while (cur >= 1) {
println("Next number: ", cur)
cur = cur - 1
}
Example 4: Sequences and functions
Our final sample program demonstrates sequences (similar to an array or list) and functions. It contains a function, sumSequence
, which takes a sequence of integers as a parameter and returns the sum of the numbers in the sequence. At the bottom, we can see our test code that creates a sample sequence and tries calling sumSequence
. If you create this file, you will see an error about the sequence indices possibly being negative – this code cannot be run without some verification work:
// #Sireum #Logika
//@Logika: --manual --background save
import org.sireum._
def sumSequence(seq: ZS) : Z = {
var sum: Z = 0
var i: Z = 0
while (i < seq.size) {
sum = sum + seq(i)
i = i + 1
}
return sum
}
////// Calling code ////////////
val list: ZS = ZS(1,2,3,4)
val total: Z = sumSequence(list)
println("Sum of elements:", total)
A few things of note here:
The definition
def sumSequence(seq: ZS) : Z
means that a function namedsumSequence
takes a parameter of typeZS
(sequence of integers,Z
= int andS
= sequence) and returns something of typeZ
(int)There is an
=
after the function header but before the opening{
of the functionThese functions are not part of a class - they are more similar to the structure in Python. We can include as many functions as we want in a file. At the bottom of the file (marked below the optional
////// Calling code ////////////
) is the calling code. When a Logika program runs, those calling code lines (which may call different functions) are executed. When the calling code lines are done, the program is done.
Logika program proof syntax
In order to prove correctness of these Scala programs, we will add Deduce blocks to process what we have learned at different points in the program. In general, every time there is an assignment statement, there will be a following Deduce
proof block updating all relevant facts.
Necessary import statements
We have already seen that we must include the import statement:
import org.sireum._
Anytime we want to do anything with the Logika proof checker. There are three other import statements that you will commonly use:
import org.sireum.justification._
is needed for any Deduce statements and basic justifications (Premise
, etc.)import org.sireum.justification.natded.prop._
is needed for any propositional logic justificationsimport org.sireum.justification.natded.pred._
is needed for any predicate logic justifications
Deduce block
Here is the syntax for a Deduce proof block:
Deduce(
1 ( claim ) by Justification,
...
)
Just as with our propositional and predicate logic proofs, we will number the lines in our proof blocks. Each line will contain a claim and a corresponding justification for that claim. We will still be able to use all of our propositional and predicate logic deduction rules, but we will learn new justifications for processing program statements. Each program may have multiple of these proof blocks to process each assignment statement.
Premise justification
Our first justification in programming logic is Premise. In a Deduce proof block, we use Premise
as a justification in the following cases:
To express an assignment statement (or other program statement)
To pull a claim established in a previous proof block into a later proof block
In both cases, the claim must capture the current value of the involved variables.
For example, consider the following program:
// #Sireum #Logika
//@Logika: --manual --background save
import org.sireum._
import org.sireum.justification._
var x: Z = 6
x = x + 1
We could insert a proof block between the two lines to express that x
currently has the value 6:
// #Sireum #Logika
//@Logika: --manual --background save
import org.sireum._
import org.sireum.justification._
var x: Z = 6
Deduce(
1 ( x == 6 ) by Premise
)
x = x + 1
But we could NOT have the same proof block after incrementing x
, since x
’s value has changed since that claim was established:
// #Sireum #Logika
//@Logika: --manual --background save
import org.sireum._
import org.sireum.justification._
var x: Z = 6
//this proof block is correct -- it captures the current value of x
Deduce(
1 ( x == 6 ) by Premise
)
x = x + 1
//NO! This statement no longer captures the current value of x
Deduce(
1 ( x == 6 ) by Premise
)
Using previous deduction rules
We can use any of our previous deduction rules in a Logika proof block. For example:
// #Sireum #Logika
//@Logika: --manual --background save
import org.sireum._
import org.sireum.justification._
import org.sireum.justification.natded.prop._
val x: Z = 6
val y: Z = 7
Deduce(
1 ( x == 6 ) by Premise,
2 ( y == 7 ) by Premise,
3 ( (x == 6) ∧ (y == 7) ) by AndI(1, 2)
)