[Instructions] [Search] [Current] [Syllabus] [Links] [Handouts] [Outlines] [Assignments] [Labs]

In defining the semantics of SIMPLE, we decided on types for a number of the semantic functions and also began defining many of those functions. In this problem, you will consider how to develop semantic functions for some of the remainder of the language.

Here are the types of the semantic functions.

meaningProg : Prog -> Input -> Output meaningSL : SL -> Cont -> Env -> Input -> Output meaningStat : Stat -> Cont -> Env -> Input -> Output meaningExp : Exp -> Env -> N meaningNum : Num -> N

(note that `meaningNum`

is built-in).

Here are the corresponding semantic domains.

Input = N* (lists of natural numbers) Output = N* (lists of natural numbers) Env = Ide -> N (functions from symbols to natural numbers) Cont = Env -> Input -> Output

You may also wish to refer to the class outline that gives an overview of the SIMPLE semantics.

SIMPLE has two kinds of conditionals.

Stat => if E then L_{1}else L_{2}fi Stat => if E then L fi

Write equations that define the semantics of these two statements.

One question that came up had to do with how to deal with Boolean values, when all we have are numbers. The answer is to use the standard C technique of having 0 represent false and every other number represent true. Some of you used positive for true, and 0 or negative for false. That's also okay.

In case you hadn't realized it, the `fi`

represents
``end of `if`

''.

For the if-then-else, we evaluate the expression and compare it to
0. If it's zero, we do the false part (using `meaningSL`

).
Otherwise, we do the true part (again using `meaningSL`

).

meaningStat [[if E then L_{1}else L_{2}fi]] = \cont . \env . \inp . (meaningExp E env) = 0 -> meaningSL L_{2}cont env inp , meaningSL L_{1}cont env inp

For the if-then-no-else, we evaluate the expression and compare it to
0. If it's zero, we go on (using the continuation). Otherwise, we
do the true part (using `meaningSL`

). To continue (in the
false case), we just apply the continuation to the current environment
and input.

meaningStat [[if E then L fi]] = \cont . \env . \inp . (meaningExp E env) = 0 -> cont env inp ; meaningSL L cont env inp

Note that in the if-then-no-else version, you still need to do something in the case that the expression evaluates to ``false''. Some of you neglected to do this.

Suppose we decide to extend SIMPLE with a simple case statement of the following form:

case (E) of Num_{1}: S_{1}; Num_{2}: S_{2}; ... Num_{n}: S_{n}esac

Show how we'd extend the abstract syntax to incorporate this new kind of statement.

Note that I've added `esac`

to clarify the end of the case
statement (mostly for uniformity).

As you may have noted, we have a list of things, so we'll need an additional syntactic domain for ``list of number/statement pairs''. I've decided that that list should be nonempty, as did most of you.

C in CaseList Stat => case (E) of C esac CaseList => V:S CaseList => V:S ; C

A number of you failed to support case statements written in the way given in the problem. Instead, you seemed to discuss them in terms of their implementation as a sequence of conditionals. The goal was really to support case statements using the structure given above. If you did this wrong, I took off on the syntax part, but was more forgiving for semantics.

Write equations that define the semantics of the new case statement.

Note that I purposefully left some of the definition of `case`

unspecified in the assignment. In particular, it was your responsibility
as semantics designer to decide what would happen if the expression did
not match any of the cases (or multiple versions of the cases).

Between the original semantics and this assignment, I moved from using
`V in Val`

to `N in Num`

for numbers. The latter
leads to some confusion with the `N`

for natural numbers,
but I'm sure you can figure it all out.

A few of you neglected to evaluate the syntactic N (using `meaningNum`

).
You need to do such evaluation before comparing to the evaluated expression.

The strategy here is to evaluate the expression and then pass it on to a ``meaning of case-lists'' function. That second function compares the value of the expression to each case value in turn. If we run out of case values, we simply continue. If there is more than one label that matches, we use the first matching label.

meaningStat [[case (E) of C esac]] = \cont . \env . \inp . meaningCases C (meaningExp E env) cont env inp meaningCases :: CaseList -> N -> Cont -> -> Env -> Input-> Output meaningCases [[N:S]] = \n . \cont . \env . \inp . n = (meaningExp N env) -> meaningStat S cont env inp , cont env inp meaningCases [[N:S;C]] = \n . \cont . \env . \inp . n = (meaningExp N env) -> meaningStat S cont env inp , meaningCases C n cont env inpt

Define `meaningExp`

, the semantic function for SIMPLE expressions.
Recall that the abstract syntax for expressions is as follows.

Exp => E_{1}+ E_{2}| E_{1}- E_{2}| E_{1}* E_{2}| E_{1}/ E_{2}| I | N | (E) where E is an element of Exp I is an element of Ide N is an element of Num

You should have been able to find this (or something close to this) in Louden. Basically, we want to do appropriate recursive calls.

We'll start with the binary expressions. In each case, we simply need to evaluate the two subexpressions in the same environment and then apply the appropriate operation.

meaningExp [[E_{1}+ E_{2}]] = \env . (meaningExp E_{1}env) + (meaningExp E_{2}env) meaningExp [[E_{1}- E_{2}]] = \env . (meaningExp E_{1}env) - (meaningExp E_{2}env) meaningExp [[E_{1}* E_{2}]] = \env . (meaningExp E_{1}env) * (meaningExp E_{2}env) meaningExp [[E_{1}/ E_{2}]] = \env . (meaningExp E_{1}env) / (meaningExp E_{2}env)

To get the value of an identifier, we simply look it up in the environment (which is the whole purpose of environments).

meaningExp [[I]] = \env . (env I)

To get the value of an expressed value (e.g., the sequence of characters
`2`

, `1`

, `5`

), we use the ``predefined''
meaning function, `meaningNum`

.

meaningExp [[N]] = \env . meaningNum N

Finally, parenthesized expressions are evaluated in the obvious way.

meaningExp [[(E)]] = meaningExp E

In some languages (such as C and its descendants) it is possible to use an assignment statement as an expression, as in

while (x := x+1) do ... od

What changes would we have to make to the SIMPLE semantics to incorporate assignment expressions? Note that you need not describe changes to the concrete syntax, but just to the abstract syntax, semantic domains, and semantic functions.

Most of you did sufficiently badly on this question that (1) I did not count it in your homework grade and (2) we'll spend some time on it in class. Those of you who got it write received some extra credit on the homework (typically, raising your grade by 1/2 a letter grade).

The typical answer suggested that we need only extend the syntax and
then add a new definition of `meaningExp`

for the assignment
expression, something like

meaningExp[[I = E]] = \env . setenv env I (meaningExp E env)

However, this does not propagate the change to the environment. In effect, you're saying ``change the environment, but go on with the old environment''. Remember: this is mathematics, so there are no side-effects.

You'll need to begin with design decisions. First of all, do we allow two different kinds of assignment (one for assignment statements and one for assignment expressions). If we disallow assignment statements, we'll need to think about how to get the equivalent. Hence, it's probably best to just add the new assignment expressions and let the parser figure out the context.

There are a surprising number of changes we'll have to make. Clearly, we need to update the abstract syntax of expressions to add the rule

Exp => I = E

But the addition of assignment is more complex than that. In particular, assignment will usually update the environment. This means that the semantic function for expressions will need to take a continuation as an argument. What do we return if we take a continuation as an argument, though? One possibility is to take ``everything else'', including input, and return output.

meaningExp : Exp -> Env -> Input -> ExpCont -> Output ExpCont = N -> Env -> Input -> Output

This also means that any place that we call `meaningExp`

, we'll
need to update the call to pass in a continuation.

Another option is to return a value/new-environment pair, and use those appropriately. For example,

meaningExp : Exp -> Env -> (Env x N)

Again, any place that we call `meaningExp`

, we'll need to
update the call to take two results and use them appropriately.

We can see this in the evaluation of expressions themselves. Before, we had not specified which argument to plus (for example) was evaluated first. Now, we need to think about whether we want it specified and, if not, how to avoid that ordering. Since it's easier to specify ordering, I'll do so in this example. You might want to consider how we might avoid doing so.

meaningExp [[E_{1}+ E_{2}]] = \env . (\(env1,val1) . (\(env2,val2) . (env2,val1+val2)) meaningExp E_{2}env1) (meaningExp E_{1}env)

or

meaningExp [[E_{1}+ E_{2}]] = \env . let (env1,val1) = (meaningExp E_{1}env) in let (env2,val2) = (meaningExp E_{2}env1) in (env2,val1+val2)

In the continuation version, it would be something like

meaningExp [[E_{1}+ E_{2}]] = \econt . \env . \inp . meaningExp E_{1}env inp (\n_{1}. \env_{1}. \inp_{1}. meaningExp E_{2}env_{1}inp_{1}(\n_{2}. \env_{2}\inp_{2}. econt (n_{1}+n_{2}) env_{2}inp_{2}

Similarly, we'll need to make changes wherever `meaningExp`

is called. One particularly important place is in the original
assignment statement (which we've decided to keep). Another is in the
`write`

statement. We'll just look at the update to
assignment.

meaningStat [[I = E]] = \cont . \env . \inp . meaningExp E env inp (\n . \env' . \inp' . cont (setEnv env' I n) inp'

That is, compute the meaning of the expression. Let the value be
`n`

and the updated environment and input be `env'`

and
`inp'`

. Then continue
with the program, after further updating the environment so that it maps
`I`

to `n`

.

`let`

A close reading of the Scheme semantics suggests that there's no formal
description of what happens with `let`

. Why not?

`let`

(in all its glorious forms) is really just a macro
defined in terms of `lambda`

and other ``primitives''
for which we've assigned semantics. For example,

(let ( (I_{1}E_{1}) ... (I_{n}E_{n}) ) E)

is just a clearer shorthand for

( (lambda (I_{1}... I_{n}) E) (E_{1}... E_{N}) )

The formal semantics gives a slightly different explanation.

Describe, in English, what's happening in the definition of cwcc in the left column of p. 43 of the Scheme semantics.

Note that there seem to be no paramters in the definition of
*cwcc*. That's because none are needed yet. The
function of two arguments is built from that

\

ek

This is then processed by *onearg* to make it
take an `E*` as its first parameter. Why? Because
the general model in Scheme is that you can apply any function
to any argument, with the semantics telling you what happens.

What next? Starting at the top, we see that there's a test to make sure
that the one argument is a function. This is to make sure that
**cwcc** is called with the appropriate argument.
Officially, the argument to `call/cc`

(which
**cwcc** models) is a function of one
argument (with that argument being a continuation).
`call/cc`

then calls the argument, using the ``current
continuation''.

What next? We grab the current store! This might mean that the
``continuation'' of `call/cc`

includes not just
the expression continuation, but also a store. (Later, we'll
see that only the expression continuation seems to be used.)

Next, we need to make sure that there's room in memory. Why do we do so? Because we're creating a new function (a continuation), and need space for that function.

Next, we apply the argument (the function) to the continuation
(which we still need to build) using *applicate*.
The parameters to *applicate* are the function
to apply, the argument list (in this case, the continuation
shoved into a list), the expression continuation, and (in this
case) the store. (As in previous cases, this is the hidden
store from `C`.)

Note that there are two calls to *new* *s*.
Both are expected to return the same thing. The second call updates the
store so that the cell is used. The first is used in building the
continuation.

We need to package up the continuation as a function (which we know that we can do because there's free memory). Recall that a function is a pair of location and ``function code''. Hence,

news|L

allocates the location for the function.

Finally, the

\

e*k'.ke*

says exactly what you should expect. ``When you apply the
encapsulated
continuation (which, like everything else, we apply to a sequence
of expressions with a following continuation), you throw away
the new continuation (*k'*), and use the original
continuation (*k*)''.

**History**

- Created Monday, March 8, 1999.
- Updated Wednesday, March 10, 1999. (Filled in answers to question 2.)
- Updated Friday, March 12, 1999. (A few minor changes.)
- A few more minor changes on Monday, March 15, 1999.

[Instructions] [Search] [Current] [Syllabus] [Links] [Handouts] [Outlines] [Assignments] [Labs]

**Disclaimer** Often, these pages were created ``on the fly'' with little, if any, proofreading. Any or all of the information on the pages may be incorrect. Please contact me if you notice errors.

This page may be found at http://www.math.grin.edu/~rebelsky/Courses/CS302/99S/Assignments/notes.03.html

Source text last modified Mon Mar 15 10:11:59 1999.

This page generated on Wed Apr 7 16:41:05 1999 by SiteWeaver. Validate this page's HTML.

Contact our webmaster at rebelsky@math.grin.edu