ARRAY BEGIN BODY ELSE
ELSIF END EXIT IF
LOOP PROCEDURE READ RECORD
THEN TYPE WHEN WRITE
PACKAGE PRIVATE IN OUT
IS OF
Constants are either integer, float, or string. Integer
constants contain only digits. Float constants contain a decimal
point. At least one digit must both precede and follow the
decimal point. String constants begin and end with a double
quote (") and contain any sequence of printable characters. If a
double quote appears inside a string constant, it must be
repeated (e.g. """Help!"" he cried"). The allowable range of
integer constants and the range and precision of float constants
are implementation-defined.
Let letter = 'A'..'Z','a'..'z'; digit = '0'..'9'; using a regular expression notation in which ',' represents set union , '.' represents set product, '*' represents Kleene closure, LAMBDA (λ) represents the null string, NOT represents set complement, and literals are delimited by quotes ('), the above definitions may be made more precise:
CONSTANT = INTCONST , FLOATCONST , STRCONST
INTCONSTANT = digit . (digit)*
FLOATCONSTANT = INTCONSTANT . '.' . INTCONSTANT
STRCONSTANT = '"' . (NOT('"') , '"' . '"')* . '"'
Identifiers are strings of letters and digits starting with
a letter (not to include the reserved keywords). Thus
identifiers can be specified as follows, where RESERVED
represents the set of reserved keywords:
IDENTIFIER = (letter , (letter , digit)*) - RESERVED
The length of an identifier must not exceed 32 characters, but
all characters of a legal identifier must be used in determining
its uniqueness.
The following are the remaining operators and delimiters ($EOF$ represents end-of-file):
ASSIGNOP = ":="
MULTOP = "*" , "/"
PLUSOP = "+" , "-"
RELOP = "<" , "<=" , ">" , ">=" , "=" , "/="
DELIM = ".." , ":", ";" , "," , "." , "(" , ")" , "$EOF$"
Adjacent identifiers, reserved words, and numeric literals
must be separated by white space (or comments). This guarantees
that there is no ambiguity in how tokens are scanned (e.g.
'begina' is one identifier, not 'begin' followed by 'a').
*** In CS524, we will implement a specific type of Boolean since we will not work with Floats and I think it's important to deal with more than just integer types. ks **
TYPE IS ;
TYPE fubar IS integer;
defines a new type, fubar, which behaves exactly like an integer.
To describe more complex type structures, Macro provides two type
specification forms for creating either a record or an array.
Example:
TYPE arr1 IS ARRAY(1..9) OF integer;
TYPE arr2 IS ARRAY(0..1) OF arr1;
TYPE arr3 IS ARRAY(0..1) OF ARRAY(1..9) of integer;
TYPE IS
RECORD
END RECORD;
For example:
TYPE R1 IS RECORD
i, j : float;
c : integer;
END RECORD;
TYPE R IS RECORD
e : integer;
f : r1;
END RECORD;
A component declaration is syntactically identical to a
variable declaration (see below). The field names within a
record are local to that record and need be unique in that
record only. As with arrays, a particular component may
also be specifiec by a record form.
id, id, ..., id : ;
Example:
a, b : integer;
c : RECORD
a, b : integer;
END RECORD;
If we use the above definition of record r to obtain
a, b : ARRAY(1 .. 10) OF r,
all of the following are legal names:
a(1).e, a(1).f.c., b(I).f
but the following are illegal:
b(1).i, a.e, a.e(1), f.j.c
Operators at the same precedence level are evaluated from left to right. All operators are left-associative except the relational operators, which are not associative at all.
Example:
Given TYPE t1 IS ARRAY(1..10) OF float;
TYPE t2 IS ARRAY(1..10) OF float;
a, b : t1;
c, d : t2;
e, f : ARRAY(1..10) OF float;
a := b, c := d and e := f are legal,
but a := c and c := e are illegal.
There is however, one conversion which the compiler should recognize and perform. Namely, when an arithmetic expression contains both integers and floats, then the integers shall be coerced into floats and the result given as a float. Take special note that the reverse is not true, that is a float is never coerced into being an integer.
IF Boolean Expression THEN
sequence of statements
ELSIF Boolean Expression THEN
sequence of statements
.
.
.
ELSIF Boolean Expression THEN
sequence of statements
ELSE
sequence of statements
END IF
The ELSE clause may be omitted. The semantics are the same as
for the IF statement in PASCAL.
: LOOP
sequence of statements
END LOOP;
This is a "loop forever", but EXIT can be used to terminate
iteration (see below). The "
READLN
statement. writes in SPIM can either be WRITE or WRITELN ks 8/93 **
In programming language design, a recurring problem is whether to treat READ and WRITE as statement types, or as predefined procedures. There are problems with both approachs. Considering READ and WRITE to be predefined procedures (as Ada does) isn't really satisfactory because (unlike ordinary procedures), we'd like them to take an indefinite number of arguments and perhaps allow non-standard parameter syntax (such as "width specifications"). On the other hand, reserving READ and WRITE seems to obviate the chance of allowing users to extend these statements to handle new data types. In Macro, we'll essentially treat READ and WRITE as predefined procedures that are overloaded. To allow lists of input or output items (which Ada doesn't really support) for the commonly used READ and WRITE procedures, we'll introduce a notational extension in which only the procedures for READ and WRITE may contain an arbitrarily long list of parameters. Thus
WRITE ("Date =", month, day, year);
would be allowed.
In Macro, only scalars may be read. That is, the standard package contains definitions for READ that accept integer and float values. READ is not predefined to handle arrays or records, though user-defined routines for such types can be written.
READ can handle components of structured types, as long as the components are scalars. Our preprocessor forces READs to handle items left to right so, e.g.,
READ (i, a(i)); is legal.
At the beginning of every subprogram is a list of all the formal parameters of the procedure. For each parameter its position, name, type and mode (IN, OUT, or IN OUT) is specified (the default is IN).
Example:
PROCEDURE F (x : float; y, z : IN OUT integer)
Three formal parameters (x, y, z) are declared. All formal
parameters are considered to be local to the body of the
subprogram.
The types of all formal parameters must be specified with a type name. An explicit type generator may not be used.
Macro subprograms follow the same scope rules as Pascal: Every variable is automatically imported into any function, or procedure contained within the definition, unless that inner definition contains another declaration of the same variable. Macro allows no forward references, so the scope of a declaration actually extends from the point of its definition to the end of its containing scope.
A procedure can also declare local constants, variables, types and procedures. The general structure of a procedure body definition is like that of PASCAL:
PROCEDURE ( ) IS
type, variable and subprogram declarations
BEGIN
Statement list
END;
The (
PROCEDURE ( );
A conditional EXIT is implied by a "WHEN clause". This is of the form:
EXIT WHEN
The EXIT is performed only if the Boolean expression is
true. Why Ada doesn't simple use an IF statement is an
interesting question. It appears they decided to "go for
barroque."
PACKAGE IS
type, variable and subprogram declarations
PRIVATE
type declarations
BODY
type, variable and subprogram definitions
BEGIN
Statement list
END;
The PRIVATE part may be ommitted if there are no private types.
The type, variable and subprogram headers that appear in package declaration section can be made visible to other packages. They are therefore called the visible part of the package. Objects can be referenced by qualifying the object's name with the name of the package in which it appears. Thus P.A would name object A in package P.
We wish to use packages to implement abstract data types, but this leads to a problem. If we place the definition of a type in the visible part of a package, it's implementation is visible outside the package. This is undesirable, as we wish to characterize an abstract data type by its operations, not its implementation. Declarations placed in the body of a package are never visible outside the package, so the definition of an abstract can't be placed there. The PRIVATE part of a package declaration exits to address this problem. In the visible part of the package, the type is decalared to be PRIVATE. The implementation of the type is hidden in the PRIVATE part. Thus is the declaration
PACKAGE SetStuff IS
-- This is the visible section
TYPE Set IS PRIVATE;
PROCEDURE InSet (i : integer; s : Set; j : OUT integer);
PRIVATE
-- This section shows the implementations of items
-- in the visible section if the implementation wasn't
-- given there.
TYPE Set IS ARRAY(1 .. 10) OF integer;
PROCEDURE InSet (i : integer;
s : Set;
j : OUT integer) IS
BEGIN
j := s(i);
END;
BODY
-- The body section is for initialization, first
-- the declarations are given...
i : INTEGER;
BEGIN
-- and then the code!
READ (i);
s(i) := 1;
END;
The name is made visible, but not the fact that it is
implemented as an array. If the full declaration were in the
visible part, its implementation would be visible. By making
types private, we can guarantee that they are manipulated only by
operations defined in the package itself. All declarations in
the package body are completely hidden outside the package.
Declarations in the specification part are visible in the body,
which is responsible for implementing the subprograms declared in
the specification part.
The statements (if any) in the package body following the declarations are executed when the package body is executed. Package bodies are executed in the order in which package bodies are sequenced. Packages declarations may appear anywhere that a procedure, type or variable declaration can appear, and thus may be nested.
program -> {package-decl} $EOF$
package-decl -> PACKAGE IDENTIFIER IS {declaration}
[private-decl] [body] END ;
private-decl -> PRIVATE {declaration}
body -> BODY {declaration} [ BEGIN statement {statement} ]
declaration -> IDENTIFIER {, IDENTIFIER} : type ;
-> TYPE IDENTIFIER IS type ;
-> PROCEDURE IDENTIFIER [formals-list] [proc-body] ;
-> package-decl
type -> variable
-> PRIVATE
-> ARRAY ( expression .. expression ) OF type
-> RECORD component {component} END RECORD
component -> IDENTIFIER {, IDENTIFIER} : type ;
proc-body -> IS {declaration} BEGIN statement {statement} END
formals-list -> ( formal-param {; formal-param} )
formal-param -> IDENTIFIER {, IDENTIFIER} : [mode] type
mode -> IN OUT
-> IN
-> OUT
statement -> READ ( expression {, expression} ) ;
-> WRITE ( write_expr {, write_expr} ) ;
-> IF boolean THEN statement {statement}
{ELSIF boolean THEN statement {statement}}
[ELSE statement {statement}] END IF ;
-> variable [:= expression] ;
-> [IDENTIFIER :] LOOP statement {statement}
END LOOP ;
-> EXIT [IDENTIFIER] [WHEN boolean] ;
write_expr -> STRINGCONSTANT
-> expression
boolean -> expression relational-op expression
relational-op-> >
-> <
-> =
-> >=
-> <=
-> /=
expression -> expression add-op expression
-> expression mult-op expression
-> ( expression )
-> [unary-op] expression
-> variable
-> INTCONSTANT
-> FLOATCONSTANT
unary-op -> -
-> +
add-op -> +
-> -
mult-op -> *
-> /
variable -> IDENTIFIER var-rest
var-rest -> ( expression {, expression} ) var-rest
-> . variable
->
If you look carefully at the above BNF it is apparent that
it will allow the construction of some syntactic structures which
are illegal. While this is true, it is being done for two
reasons: