1. A Typed Expression Grammar
Build an attribute grammar for expressions to determine the type of each expression. You may assume that each identifier has a type attribute. Your grammar should use the coercion rules of Java (you'll need to look them up) and support the types byte, short, int, long, float, double, boolean, and String. You may also want to add an error type to indicate that an expression cannot be typed.
Your grammar should support the operations +, -, *, /, <, >, == with appropriate associativity and precedence. Your grammar should also support parenthesization.
For this, we'll need to extend the original BNF grammar to accomodate the new types of operations and then extend the grammar with attributes and rules for computing the attributes.
The original grammar was
Exp ::= Exp AddOp Term
| Term
Term ::= Term MulOp Factor
| Factor
Factor ::= id
| '(' Exp ')'
Traditionally, comparison operations have lower precedence than arithmetic
operations. This means that they come before AddOp in the
expression grammar. Hence, we'll add a nonterminal Stuff that
replaces the old Exp and redefine Exp to permit
relational operations.
While most relational operators can't appear
multiple times. For better or for worse, 3 < x < 5
is interpreted as (3 < x) < 5 and not as
(3 < x) and (x < 5). We can handle that problem at
the syntactic level (in the grammar) or the semantic level (in the
attributes).
Unlike <, == can appear multiple times.
We'll make it left associative.
Exp ::= Exp '==' Stuff
| Stuff RelOp Stuff
| Stuff
Stuff ::= Stuff AddOp Term
| Term
Term ::= Term MulOp Factor
| Factor
Factor ::= id
| '(' Exp ')'
RelOp ::= '<'
| '>'
AddOp ::= '+'
| '-'
MulOp ::= '*'
| '/'
Now, we need to consider coercion in Java. I actually gave you the
standard Java coercions for numbers. That is, bytes are
automatically coerced to shorts when they appear in an
expression with shorts. In addition, bytes can be
coerced to anything that shorts can be coerced to.
The rest goes short to int to long
to float to double. All of these can appear
with any of the operations listed.
Any of the primitive types can be coerced to String when
added to a String. Strings can be compared with
== but not with the other comparison operations.
Strings cannot be subtracted, multiplied, or divided.
What's left? Values of type boolean cannot be coerced
to anything except String. We'll also say that any
operation that involves our special error type is
an error.
To encompass the limited operations available for String,
we'll modify our grammar to directly mention which addition operation
is used.
I'm also going to make use of a "most general numeric type" method to keep my code a little bit shorter.
mostGeneralNumericType(alpha,beta)
begin
foreach type in (double, float, long, int, short, byte) do
if (alpha is type) or (beta is type) then
return type
end if
end foreach
return error
end
Given that method, here's the attribute grammar.
Exp0 ::= Exp1 '==' Stuff
Exp0.type =
if ( (Exp1.type is error) or (Stuff.type is error) ) then
error
else if ( (Exp1.type is String) and (Stuff.type is String) ) then
boolean
else if ( (Exp1.type is String) or (Stuff.type is String) ) then
error
else if ( (Exp1.type is boolean) and (Stuff.type is boolean) ) then
boolean
else if ( (Exp1.type is boolean) or (Stuff.type is boolean) ) then
error
else
boolean
Exp ::= Stuff RelOp Stuff
Exp.type =
if ( (Exp1.type or Exp2.type is in {String,error,boolean) ) then
error
else
booelan
Exp ::= Stuff
Exp.type = Stuff.type
Stuff0 ::= Stuff1 '+' Term
Stuff0.type =
if ( (Stuff1.type is error) or (Term.type is error) ) then
error
else if ( (Stuff1.type is String) or (Term.type is String) ) then
String
else
mostGeneralNumericType(Stuff1.type, Term.type)
Stuff0 ::= Stuff1 AddOp Term
Stuff0.type =
if ( (Stuff1.type is error) or (Term.type is error) ) then
error
else
mostGeneralNumericType(Stuff1.type, Term.type)
Stuff ::= Term
Stuff.type = Term.type
Term0 ::= Term1 MulOp Factor
Term0.type =
if ( (Term1.type is error) or (Factor.type is error) ) then
error
else
mostGeneralNumericType(Term1.type, Factor.type)
Term ::= Factor
Term.type = Factor.type
Factor ::= id
Factor.type = id.type
Factor ::= '(' Exp ')'
Factor.type = Exp.type
2. An Expression Compilation Grammar
Build an attribute grammar that translates arithmetic expressions over floating point variables to corresponding assembly code that, when executed, would evaluate those expressions. Expressions should be allowed to include +, -, *, /, and parentheses.
You should use the following operations for a single-register machine (in which that single register is called the accumulator).
set X
sto X
add X
subtract X
multiply X
divide X
label N
JMP N
JMZ N
NOOP d
It turns out that we'll need to generate temporary locations for some
operations. For example, in id * id + id * id, we'll
need to store the results of one multiplication somewhere before we
do the other multiplication. This means that we'll need to generate
temporary variable names. We'll also need to know where the results
of any subexpression are stored. Hence, in addition to a
code attribute for each nonterminal, we'll also
need a location attribute.
To help ensure that
numbering of temporary variables is unique, we'll also use a number of
tempcount attributes. Since expressions may
want to change this attribute, we'll use tcIn as
the count before an expression and tcOut afterwards.
How can we initialize tcIn
to some value? By adding an additional nonterminal, Start
to the language. The goal will be to generate a Start with
its accompanying code.
We're not going to generate the most efficient code. In particular, we may store intermediate results in temporary variables even when we don't need to do so. Because of this, we will most likely generate significantly more temporary variables than we need to.
Start ::= Exp
Exp.tcIn = 0
Start.location = Exp.location
Start.code = Exp.code
Exp0 ::= Exp1 AddOp Term
Exp1.tcIn = Exp0.tcIn
Term.tcIn = Exp1.tcOut
Exp0.tcOut = Term.tcOut + 1
Exp0.location = "_temp_" + Term.tcOut
Exp0.code =
Exp1.code +
Term.code +
set Exp1.location +
AddOp.op + Term.location +
sto + Exp0.location
Exp ::= Term
Term.tcIn = Exp.tcIn
Exp.tcOut = Term.tcOut
Exp.location = Term.location
Exp.code = Term.code
Term0 ::= Term1 MulOp Factor
Term1.tcIn = Term0.tcIn
Factor.tcIn = Term1.tcOut
Term0.tcOut = Factor.tcOut + 1
Term0.location = "_temp_" + Factor.tcOut
Term0.code =
Term1.code +
Factor.code +
set Term1.location +
AddOp.op + Factor.location +
sto + Term0.location
Term ::= Factor
Factor.tcIn = Term.tcIn
Term.tcOut = Factor.tcOut
Term.location = Factor.location
Term.code = Factor.code
Factor ::= id
Factor.tcOut = Factor.tcIn
Factor.location = id.name
Factor.code = ""
Factor ::= '(' Exp ')'
Exp.tcIn = Factor.tcIn
Factor.tcOut = Exp.tcOut
Factor.location = Exp.location
Factor.code = Exp.code
3. User Defined Types
Why might someone suggest that a programming language should not support user-definable types? How might you convince them that their reasoning is misguided?
Someone might suggest that we don't need user-defined types since they're simply combinations of existing types, so we might as well just use appropriate combinations of existing types (passing around sets of variables whenever necessary). Someone might also suggest that as we add user defined types, we increase the complexity of the compiler, making type checking harder. Finally, it could be argued that user-defined types complicate the language.
Arguments avoided.
4. Describing Meta-Types
Some languages permit programmers to write their own type constructors.
For example, one might be able to define List(x) where
x is any type. Having done so, it then becomes possible
to write things like
type
intlist = List(integer);
var
names = List(string);
These type constructors are often called meta-types.
Suppose you were required to extend Pascal so that it supported
user-definable type constructors. What syntax would you use for
such constructors (i.e., how does one define such a type constructor
on par with record and array?) What other
issues might one need to consider in adding such meta-types to
Pascal?
If your answer is based on experience with user-definable type constructors in another language, note which language you've based your answer on.
As with many problems, the main goal of this one is to get you thinking about the issues. Seeing my answer won't really help you think about the issues. Come talk to me if you have have questions.
5. Why Have Meta-Types
Why might a group of language designers decide to include meta-types in a programming language? How might you convince them that this is a bad idea?
Your answer to this question should be well-reasoned and should accomodate the statements and responses an opponent of user-definable types might give.
See my answer to problem 4.
6. Type Coercion
Write a program that reads sequences of the form
type + type + type + ... + type
and reports on any type coercions that must occur for that sequence
to be interpreted. You may decide which type coercion strategy to use,
but it should be reasonable and well-documented. You should support
integers, reals, and strings.
For example, your program might generate the following
int + int + real + int + string
^^^^^^^^^ coerce to real
^^^ coerce to real
^^^^^^^^^^^^^^^^^^^^^^ coerce to string
Your program need not generate output in this form. Your goal is to make it clear which coercions are necessary. This is simply an illustration of one way to do that.
You folks are the ones who like to code.
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.
Source text last modified Sun Feb 22 20:53:37 1998.
This page generated on Sun Feb 22 20:56:48 1998 by SiteWeaver.
Contact our webmaster at rebelsky@math.grin.edu