UP | HOME

Operational Semantics
Lecture 3

Table of Contents

Review

  • Brainstorming a language
  • Symbols vs. meaning
  • Informal descriptions (C standard)

Questions about the last class?

Quiz

Quiz Discussion

Operational Semantics

Notation

  • <symbols> => value
    • "Symbols" evaluate "value"
    • e.g., ASCII evaluates to machine representation or mathmetical object

Number symbols

  • <2> => 2
    • The symbol for two evaluates to the value 2
  • Other symbols for two
    • <two> => 2
    • <二> => 2

Lots of rules for numbers

  • <3> => 3
  • <47> => 47
  • How can we define all number symbols?

Meta-symbols

  • n :: 0 | 1 | 2 | … | 47 | …=
    • We can use a regular expression for a finite description
  • <n> => n
    • Symbols for n evaluates to the value n
    • Use same meta-symbol for symbol and value for convenience
  • Use n1, n2, etc., when there are multiple numbers involved

Defining operator symbols

<n1 plus n2> => ?

Use mathmetical operations

  • <n1 plus n2> => n1 + n2
  • Can also just use same symbols in our language for convenience
    • <n1 + n2> => n1 + n2

Proof rules

Proof rule notation

n = n1 + n2      // assuming that n equals n1 plus n2
--------------   // we can conclude that
<n1 + n2> => n   // the symbols <n1 + n2> evaluate to n

Example

n = 1 + 2      // recall that this is the math operator +
------------
<1 + 2> => n   // recall that <1 + 2> are symbols

Axioms


               // No assumptions
--------
<1> => 1       // Conclusion is defined to be always true


--------
<2> => 2


--------
<seven> => 7


--------
<20> => 5      // if you want to make your language confusing

A conclusion without any other assumptions (premises) is defined to be true

For numbers, these are like saying ASCII "2" evaluates to the machine representation for 2

Logical arguments aren't self-justifying, they just mean the argument follows from assumptions. Usually we want at least consistency (no contradictory conclusions).

Permitting contradictions means anything is provable https://math.stackexchange.com/questions/5564/why-an-inconsistent-formal-system-can-prove-everything?noredirect=1&lq=1

i want to prove b pigs can fly

a is true and ¬ a is true

a is true

(b -> a) is true not (a -> not b) is true not (b -> a) is true

Expressions

  • We can use a grammar to define syntax for expressions

    e ::= e + e | e - e
        | e * e | e / e
        | (e)   | n
    

Recall that "::=" and pipe "|" are part of the grammar metalanguage

e is the metasymbol for expressions (nonterminal in grammar terminology)

+ - * / ( ) are all the symbols in the language

Expression example

<(1 + 2) * 7> => 21

Proof rules for arithmetic

<e1> => n1     <e2> => n2     n = n1 * n2
-----------------------------------------
<e1 * e2> => n

Assuming that <e1> => n1, <e2> => n2, and n = n1 + n2

We can conclude that <e1 * e2> evaluates to n

Proof rules for arithmetic

<e1> => n1     <e2> => n2     n = n1 * n2
-----------------------------------------
<e1 * e2> => n


<e1> => n1     <e2> => n2     n = n1 + n2
-----------------------------------------
<e1 + e2> => n

Similarly for other arithmetic operations

Using proof rules

We'll prove that (1 + 2) * 7 evaluates to 21

<(1 + 2) * 7> => 21

We can work backwards





<(1 + 2) * 7> => 21

Apply the multiplication rule



<1 + 2> => 3                       <7> => 7     21 = 3 * 7
----------------------------------------------------------
<(1 + 2) * 7> => 21

Apply the addition rule to the remaining expression

<1> => 1    <2> => 2    3 = 1 + 2
---------------------------------
<1 + 2> => 3                       <7> => 7     21 = 3 * 7
----------------------------------------------------------
<(1 + 2) * 7> => 21

Think of proof rules as an interpreter

  • Evaluator walks syntax tree, returns final value
    • int evaluate(Node expression)
  • Each rule evaluates one language construct
    • Proof rules implicily use dynamic dispatch

Think of proof rules as an interpreter

// proof rule for addition

<e1> => n1     <e2> => n2     n = n1 + n2   // body of function
-----------------------------------------
<e1 + e2> => n                              // function signature



// evaluation function in C
int evaluateAdd(e1, e2) {
  n1 = evaluate(e1);      // dynamic dispatch
  n2 = evaluate(e2);
  n = n1 + n2;
  return n;
}

Think of the classic infix expression calculator example

  • Get syntax tree (or use stack to process infix expressions)
  • Evaluate each operation
  • Recursively evaluate operands

Variable substitution

Seems kind of tautological

Not too useful when symbols are the same as values

How do we model variables

x = 10 + y
print(x * 7)

How do we derive the value of <x * 7>?

Applying proof rules

???
--------
<x> => ?    <7> => 7    n = ? * 7
---------------------------------
<x * 7> => n

Modeling state

  • Variables are a mapping s from names to values
    • s' = s.put(name, value)
    • value = s.get(name)

Adding a symbol creates a fresh mapping (doesn't mutate s)

s' is "s prime", common shorthand for making a fresh symbol

lookup returns the value

Tracking state in proof rules

  • New notation
    • s : <1 + 3> => 4
    • "under the current context s, <1 + 3> evaluates to the value 4"

s is sometimes called the "context", the "environment", or a "configuration variable"

Variable expansion rule

n = s.lookup(x)   // assuming that n is the value of x in the state
---------------   // we can conclude that
s : <x> => n      // <x> evaluates to the value n

Finishing the example



s : <x> => n1       <7> => 7
---------------------------
s : <x * 7> => n

Finishing the example

n1 = s.lookup(x)    // apply variable substitution rule
----------------
s : <x> => n1       <7> => 7    n = n1 * 7
---------------------------
s : <x * 7> => n

Variable assignment

So we have some notation for defining semantics as an evaluator. Who cares?

Can start to notate differences in language design choices.

For example, the behavior of variable assignment.

Example

x = 10 + y
print(x * 7)

Assignment rule

s : <x = 10 + y> => ??

What does it mean to evaluate an assignment?

Some choices

  • C-like, x = y = z, assignment is an expression that returns a value
  • Only updates context (side-effects)
  • No updates to context (no side-effects, functional-style let-binding expressions)

One choice: update state for later use

s : <x = 10 + y> => s'

Imperative-style: update memory location. Later expressions can access memory via variable substitution.

When to evaluate the definition?

// evaluate at define-time
<e> => n     s' = s.put(x, n)
----------------------
s : <x = e> => s'

How should variable assignment work?

A couple of choices:

  • evaluate rhs at define time
  • evaluate rhs at use time

For instance, make has both https://www.gnu.org/software/make/manual/html_node/Flavors.html#Flavors

When to evaluate the definition?

// evaluate at define-time (eager evaluation)
<e> => n     s' = s.put(x, n)
----------------------
s : <x = e> => s'



// store expression for evaluation at use-time (lazy evaluation)
s' = s.put(x, e)       // store expression itself
----------------------
s : <x = e> => s'

Storing expression requires a different kind of store, one that maps symbols to expressions rather than values.

Ramifications of lazy evaluation

  • What should be printed here?
y = 1
x = 10 + y
y = 2
print(x * 7)

Do we use the first definition of y? The second?

Side effects complicate lazy evaluation

One solution: no side effects, i.e., no reassignment of variables

Potential personal project: implement alternative variable evaluation semantics on top of our common language

Example of eager evaluation






<e> => n                 s.put(x, n)
------------------------------------
s     : <x = e>      => s'

Example of eager evaluation





<10 + y> => ??           s.put(x, ??)
-------------------------------------
{y=1} : <x = 10 + y> => {y=1; x=??}

Example of eager evaluation



<10> => 10   <y> => ?
---------------------
<10 + y> => ??           s.put(x, ??)
-------------------------------------
{y=1} : <x = 10 + y> => {y=1; x=??}

Example of eager evaluation

             1 = s.lookup(y)
             ---------------
<10> => 10   <y> => ?
---------------------
<10 + y> => ??           s.put(x, ??)
-------------------------------------
{y=1} : <x = 10 + y> => {y=1; x=??}

Example of eager evaluation

             1 = s.lookup(y)
             ---------------
<10> => 10   <y> => 1
---------------------
<10 + y> => 11           s.put(x, 11)
-------------------------------------
{y=1} : <x = 10 + y> => {y=1; x=11}

Additional Resources

Homework

Write a proof that 一加二乘三 evaluates to 7, i.e., <一加二乘三> => 7, using the following semantic rules


--------------
<一> => 1


--------------
<二> => 2


--------------
<三> => 3


<e1> => n1   <e2> => n2   n = n1 + n2
-------------------------------------
<e1 加 e2> => n


<e1> => n1   <e2> => n2   n = n1 * n2
-------------------------------------
<e1 乘 e2> => n

Author: Paul Gazzillo

Created: 2022-02-14 Mon 11:49