The number of points for each modification roughly reflects the amount of effort that I expect it will take to implement the modification.
i := x + y; // this is a comment j := a - b; // this computes "a + (-b)" // a comment; still in the comment // and more comment
<if stmt> ::= if <test> then <stmt list-1> [else <stmt list-2>] endThe semantics are as expected: if the <test> evaluates to true, execute the statements in <stmt list-1>; otherwise, execute the statements in <stmt list-2>, if any. (Note: the -1 and -2 do not represent new syntactic entitites, but rather distinguish two entities that have the same syntax.)
These extensions mean that the PL/0 programmer can declare variables or formal parameters to be either integers, intervals or booleans, and use them in essentially all the ways in which integers (the only previously available first-class type) can be used, except:
Add the literal constants true and false of type Boolean to the language.
Here are some examples of Booleans in legal and illegal uses:
var P,Q: interval; P := false or 1>0; // P is assigned to P := predicate(17, true, 0=1); // calls function predicate output := false; // illegal if (P) then output := 1; // legal
A value of type interval is constructed with a call to an intrinsic function named interval in an expression using the syntax:
interval ( < expr0 > [ , < expr1 > ] ; )This constructor looks like a call to the function named interval, with two or one arguments. (The expr1 is optional.) If two arguments are given, then the inverval's lower bound is the value of expr0 and the upper bound is the value of expr1. If only one argument is given, then the interval's lower bound and upper bound have the same value given by expr0. An error shall be flagged, either at compile time or at run time, if the interval is constructed such that the given lower bound is greater than the given upper bound.
Values of type integer may be freely promoted to values of type interval, as if the interval constructor with one argument were used.
Values of type interval may not be freely demoted to values of type integer. Instead, use the intrinsic function of one argument lb(ub) to return the lower(upper) bound from the interval argument.
Here are some examples summarizing declarations and conversions:
var X,Y: interval; var A: integer; ... A := 17; X := A; // equivalent to X := interval(A) X := interval(2, 3); A := X; // illegal, since this is an implicit demotion A := lb(X); // legal, A now has value 2 A := ub(A); // equivalent to A := ub(interval(A))
The unary and binary operators that currently work on integer values are also overloaded to combine interval values. If the arithmetic operator (such as +) yields an integer when given integer(s), then the same operator yields intervals when given intervals. If the relational operator (such as =) yields a Boolean when given integer(s), then the same operator also yields a Boolean when given intervals.
Operators on interval values always work to pessimize the interval, namely to make the resulting interval as wide as possible, or, put another way, to maximize the uncertainty of a value.
Continuing the example:
interval(2,3) + interval(5,10) // evaluates to (7, 13) interval(10,20) / interval(1,2) // evaluates to (5, 20) interval(10,20) * -1 // evaluates to (-20, -10) interval(10,20) < interval(20,30) // evaluates to false interval(10,20) <= interval(20,30) // evaluates to true interval(10,22) < interval(20,30) // evaluates to false interval(21,22) < interval(20,30) // evaluates to false
The new binary infix operator in takes two interval values, and checks to see if the left operand is embedded within the right operand, and returns a Boolean:
interval(10,20) in interval(0,30) // evaluates to true interval(10,30) in interval(0,30) // evaluates to true interval(10,31) in interval(0,30) // evaluates to false interval(-1,30) in interval(0,30) // evaluates to false
An error shall be flagged, either at compile time or at run time, if an interval division operator is given a denominator that is an interval that contains 0.
For example, the following are legal function declarations:
procedure foo(x:interval, y:integer): integer; begin <stmt list> end foo; procedure bar(): interval; var x:int; begin <stmt list> end bar;Function calls can be used anywhere that an expression of the appropriate type can be used. Syntactically, function calls (and calls to intrinsic functions) are sub-expressions that have higher precedence than other operators. Functions may also be called like procedures, as a separate statement; in this case the value returned from the function is discarded. The type of the result of a function can be any first class type. The result is returned by value.
To return a value from a function, the return statement is used with an expression. Both forms are captured by
<return stmt> ::= return [ <expr> ]The type of the expression returned must be freely promotable to the type declared for the enclosing function. It is illegal for a value-returning return statement to appear in a procedure not declared to return a result. Similarly, it is illegal for a non-value-returning return statement to appear in a function. It is an error if it is possible for execution to reach the end of a function's body without executing a return statement. In particular, a function whose only return statement is in a then clause of an if statement is invalid, since if the then clause is not taken then the function will not return anything.
The following illustrates some legal uses of return statements:
procedure foo(x:int,y:interval):interval; begin if x>10 then return x; end; if x< 5 then return y; end; return y * 5; end foo;
The practical impact of empty statements is that `;' can be used as both separators and terminators.
The extended language is to support a throw statement. The pseudo syntax is:
<throw stmt> ::= throw <Expr>The Expr must be of type integer. Execution of the throw statement evaluates the Expr and then "throws" that value, where the thrown value is examined by every dynamically enclosing catch statement (see below) until a catch statement recognizes that value as one it has been set up to handle. If no dynamically enclosing catch statement has been set up to handle the throw, then the entire program is to terminate with a non-zero exit code.
A throw may force procedures (and functions) to return prematurely. A throw can be thought of as a different kind of return; unlike return statements which merely return from a single procedure call, a throw statement need not necessarily return from the procedure (if the throw statement is statically embedded in a catch that handes the throw), nor it need not return from exactly one procedure.
The extended language is to support a catch statement. The pseudo syntax is:
<catch stmt> ::= catch [ ( <Expr> { , <Expr> } ) ] within <statement list> [ handler <statement list > ] endThe Expr in the optional expression list must be of type integer. The <statement list> between the in and the handler keywords is the body of the catch statement. The <statement list> between the handler and the end keywords is the handler of the catch statement. This part is optional.
Execution of the catch statement proceeds as:
Here are some examples of catch and throw:
catch (15+2) within throw 17 handler // handler for value 17, which is directly thrown end
catch (15+2) within catch (15+1) within throw 17 handler // this code is never executed ("throw 17" not caught by "catch 16") end handler // handler for value 17, which is thrown by ignored by innermost catch end
catch (15+2) within throw 17 handler throw 17 // throws to some OTHER dynamically enclosing catch statement end
401admin at cs.washington.edu