Добавил:
Upload Опубликованный материал нарушает ваши авторские права? Сообщите нам.
Вуз: Предмет: Файл:
AhmadLang / Introduction to Programming Using Java-1.pdf
Скачиваний:
71
Добавлен:
31.05.2015
Размер:
5.27 Mб
Скачать

CHAPTER 9. LINKED DATA STRUCTURES AND RECURSION

472

9.5.3Building an Expression Tree

Now, so far, we’ve only evaluated expressions. What does that have to do with translating programs into machine language? Well, instead of actually evaluating the expression, it would be almost as easy to generate the machine language instructions that are needed to evaluate the expression. If we are working with a “stack machine”, these instructions would be stack operations such as “push a number” or “apply a + operation”. The program SimpleParser3.java can both evaluate the expression and print a list of stack machine operations for evaluating the expression.

It’s quite a jump from this program to a recursive descent parser that can read a program written in Java and generate the equivalent machine language code—but the conceptual leap is not huge.

The SimpleParser3 program doesn’t actually generate the stack operations directly as it parses an expression. Instead, it builds an expression tree, as discussed in Section 9.4, to represent the expression. The expression tree is then used to find the value and to generate the stack operations. The tree is made up of nodes belonging to classes ConstNode and BinOpNode that are similar to those given in Section 9.4. Another class, UnaryMinusNode, has been introduced to represent the unary minus operation. I’ve added a method, printStackCommands(), to each class. This method is responsible for printing out the stack operations that are necessary to evaluate an expression. Here for example is the new BinOpNode class from SimpleParser3.java:

private static class BinOpNode extends ExpNode {

char op;

// The operator.

ExpNode left;

// The expression for its left operand.

ExpNode right;

// The expression for its right operand.

BinOpNode(char op, ExpNode left, ExpNode right) {

// Construct a BinOpNode containing the specified data. assert op == ’+’ || op == ’-’ || op == ’*’ || op == ’/’; assert left != null && right != null;

this.op = op; this.left = left; this.right = right;

}

double value() {

//The value is obtained by evaluating the left and right

//operands and combining the values with the operator. double x = left.value();

double y = right.value(); switch (op) {

case ’+’: return x + y;

case ’-’: return x - y;

case ’*’: return x * y;

case ’/’: return x / y;

default:

return Double.NaN; // Bad operator!

}

}

void printStackCommands() {

CHAPTER 9. LINKED DATA STRUCTURES AND RECURSION

473

//To evaluate the expression on a stack machine, first do

//whatever is necessary to evaluate the left operand, leaving

//the answer on the stack. Then do the same thing for the

//second operand. Then apply the operator (which means popping

//the operands, applying the operator, and pushing the result). left.printStackCommands();

right.printStackCommands(); TextIO.putln(" Operator " + op);

}

}

It’s also interesting to look at the new parsing subroutines. Instead of computing a value, each subroutine builds an expression tree. For example, the subroutine corresponding to the rule for <expression> becomes

static ExpNode expressionTree() throws ParseError {

//Read an expression from the current line of input and

//return an expression tree representing the expression. TextIO.skipBlanks();

boolean negative; // True if there is a leading minus sign. negative = false;

if (TextIO.peek() == ’-’) { TextIO.getAnyChar(); negative = true;

}

ExpNode exp; // The expression tree for the expression. exp = termTree(); // Start with a tree for first term. if (negative) {

//Build the tree that corresponds to applying a

//unary minus operator to the term we’ve

//just read.

exp = new UnaryMinusNode(exp);

}

TextIO.skipBlanks();

while ( TextIO.peek() == ’+’ || TextIO.peek() == ’-’ ) {

//Read the next term and combine it with the

//previous terms into a bigger expression tree. char op = TextIO.getAnyChar();

ExpNode nextTerm = termTree();

//Create a tree that applies the binary operator

//to the previous tree and the term we just read. exp = new BinOpNode(op, exp, nextTerm); TextIO.skipBlanks();

}

return exp;

} // end expressionTree()

In some real compilers, the parser creates a tree to represent the program that is being parsed. This tree is called a parse tree. Parse trees are somewhat di erent in form from expression trees, but the purpose is the same. Once you have the tree, there are a number of things you can do with it. For one thing, it can be used to generate machine language code. But there are also techniques for examining the tree and detecting certain types of programming

CHAPTER 9. LINKED DATA STRUCTURES AND RECURSION

474

errors, such as an attempt to reference a local variable before it has been assigned a value. (The Java compiler, of course, will reject the program if it contains such an error.) It’s also possible to manipulate the tree to optimize the program. In optimization, the tree is transformed to make the program more e cient before the code is generated.

And so we are back where we started in Chapter 1, looking at programming languages, compilers, and machine language. But looking at them, I hope, with a lot more understanding and a much wider perspective.