CS 496 - Lab 2

Parser Generators

In this lab you will implement a simple calculator application using JFlex and CUP.

The general idea is that you will write JFlex rules to describe the lexical structure of the expressions the calculator will evaluate, and CUP rules to describe the grammar and semantic actions which parse and evaluate the expressions.

We practiced writing JFlex rules in Lab 1 and Assignment 2.

Getting Started

Import lab2.zip into the Eclipse workspace (File->Import...->Existing Projects into Workspace->Archive File).  You should see a project called calc in your workspace.

Your Task

Your task is to use JFlex and CUP to implement a calculator.  The calculator should read in arithmetic expressions (supporting at least additive and multiplicative operators) and compute the result of the expression.  The evaluation of the expression should happen when the user types a newline (ENTER) character.

Here's an example transcript of running the calculator (user input in bold red):

2 + 3 * 4
14.0
64 / 4 / 2
8.0
- - 3
3.0
4 - 5
-1.0

Writing CUP Grammar Rules

We will discuss writing CUP rules as part of the lab.  The basic idea is to specify the productions in the expression grammar, and then associate semantic actions with each production.  The semantic actions are invoked as the parser reduces strings of symbols on the parser stack as the parser completes the reverse rightmost derivation of the input symbols.  CUP allows us to associate a Java object with each grammar symbol.  We can use these objects as synthesized attributes to convey semantic information as the parse proceeds.

Example production.  Here is a grammar rule for unary expressions:

 unary_expression ::=

NUMBER:n
{: RESULT = n; :}

| MINUS unary_expression:e
{: RESULT = new Double( e.doubleValue() * -1 ); :}

;

At the top of the CUP specification file, we declared the type of the unary_expression nonterminal symbol to be Double:

non terminal Double unary_expression;

The grammar rule says that a unary_expression is either a NUMBER (which is a terminal symbol whose value is a numeric value of type Double), or a MINUS terminal symbol followed by a unary_expression.  So, the rule defines two productions which the parser can use to reduce a unary_expression symbol.

The semantic actions are the code snippets in between the {: and :} symbols.  For our calculator application, we want the semantic actions to collect the numeric values of grammar symbols on the right hand side of the production and compute a result value.  The result value is assigned to a special symbol called RESULT, which represents the synthesized attribute value of the nonterminal symbol which is being reduced by the production.  For example, when the parser sees the symbols

MINUS unary_expression

on the stack and decides to reduce a unary_expression, applying the semantic action

{: RESULT = new Double( e.doubleValue() * -1 ); :}

the value of the left-hand unary_expression symbol's synthesized attribute is the negation of the unary_expression symbol on the right hand side of the production.

Note that we use the variable e to refer to the attribute value of the right-hand unary_expression.  When we define productions as part of a grammar rule, we can specify variable names to use to refer to the attribute values of the right-hand side symbols.  This is done by appending

:varName

to a grammar symbol on the right hand side of the production, where varName is the name of the variable by which we will refer to the grammar symbol's attribute value.

Extra Features

Once you finish the basic features of the calculator, try implementing the following features:

Parenthesized expressions.  Support explicit parentheses to allow a lower-precedence operator to be evaluated before a higher-precedence operator.

Variables.  Variables can be defined using syntax of the form ident = expression. When a variable is used in an expression, the value most recently assigned to the variable is used as the value of the expression.  References to undefined variables evaluate to zero.