Semantics: what is the actual desired behavior of the code
You can specify the semantics of a language by saying whatever is executed in the byte level is the expected behavior of the code. However, it does not enable code optimization since optimization might sometimes change small details in the exact behavior. So we need to distinguish defined behavior (we guarantee) with undefined behavior (might be optimized out)
If we were to use stack machine to define code behavior: - we might care whether to use stack machine - we might care which way stack grows - we might care how integers are represented - we might care particular instruction set of the architecture
These are too irelevant for an ordinary programmer.
Operational Semantics: describe program evaluation via execution rules on an abstract machine. It is useful for specifying implementations.
Denotational Semantics: program's text is mapped to a function in mathematical sense.
Axiomatic Semantics: define the behavior as @require
and @ensure
. Foundation of many program verification systems.
In operational semantics
environment E: a mapping between variable name to memory location. Note that we only write environment mappings that is in scope. For example E = [a : l_1, b : l_2]
store S: a mapping between memory location to value of the variable. For example: S = [l_1 \to 5, l_2 \to 7]
We also define a notation on how we update the store: we write S' = S[12/l_1] to update the value at location l_1 to 12. So we guarantee S'(l_1) = 12 \land (\forall l \neq l_1)(S'(l) = S(l))
We also need a way to represent objects: X(a_1 = l_1, ..., a_n = l_n):
X: the class of the object
a_i: the attribute (including the inherited ones)
l_i: the location where a_i exists
Special objects without attributes:
Int(5): integer with value 5
Bool(true): boolean with value true
String(4, "Cool"): string of size 4
void: no operations can be performed on it except for test isVoid()
. (or NULL
)
The above is an evaluation judgement: if evaluation of e
terminates then the value of e
is v
and the new store is S'
self
: current value of self object
E
: current variable environment
S
: current store
Notice that the
self
object andE
is not modified. The only side effect of evaluation is the value and updatedS'
.Also the expression only guarantee to hold when the evaluation actually terminates.
Note that in above example, we only evaluate by look up, we are not changing any value.
Note that the order of evaluation matters.
When we wan to allocate new variables to our environment and store, we need to define a new instruction newloc()
that allocate an return a new location.
Default Value: for each class A there is a default value D_A
D_{int} = Int(0)
D_{bool} = Bool(false)
D_{string} = String(0, "")
D_A = void
For class A, we write: class(A) = (a_1 : T_1 \leftarrow e_1, ..., a_n : T_n \leftarrow e_n)
a_i: attributes (include inherited in greatest ancester first order)
T_i: type
e_i: initializers
Note that when initializers are evaluated (and typing), only attributes are in scope.
Some error are not caught by type checker can happen run-time:
dispatch on void
division by zero
subtract out of range
heap overflow
Most language does not have a well specified operational semantics. When portability (heterogeneous device) is important, operational semantics is essential.
Table of Content