Programming Languages (CSC-302 98S)

[Instructions] [Search] [Current] [Changes] [Syllabus] [Handouts] [Outlines] [Assignments]


Notes on Assignment 3: Semantic Analysis and Types

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
set the accumulator to the value stored in variable X.
sto X
store the accumulator in variable X.
add X
add the value stored in variable X to the accumulator.
subtract X
subtract the value stored in variable X from the accumulator.
multiply X
multiply the accumulator by the value stored in variable X.
divide X
divide the accumulator by the value stored in variable X.
label N
define a label (used for branches).
JMP N
jump/branch to the line labeled N.
JMZ N
jump to the line labeled N if the accumulator has value 0.
NOOP d
do nothing.

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.


[Instructions] [Search] [Current] [Changes] [Syllabus] [Handouts] [Outlines] [Assignments]

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