上一篇文章介绍了我们这个编译器的词法分析部分,这一篇文章我们主要来讨论如何用JAVA进行表达式的计算。
在这之前我们先要有一个管理变量的地方,这里我们新建一个名为Varibles的类,其拥有一个类型为HashMap的类变量variblesMap负责来保存程序运行时的变量名和值。因为是采用Map进行管理,因此当给变量重复赋值时会自动覆盖。
package com.liu.system;
import java.util.HashMap;
import java.util.Map;
/*
* 用于存储变量的类
* 创建于2017.3.9
* @author lyq
* */
public class Varibles {
public static Map<String, String> variblesMap = new HashMap<String, String>();
}
接下来我们来进入今天的正题--表达式的计算。我们接受的输入是一个仅包含数字、加减乘除号和括号的字符串List,要求最后返回的是表达式的计算结果。如果表达式的格式不正确要能够报出相应的错误。
学过数据结构的人应该都知道,树的遍历方式有三种:前序遍历、中序遍历以及后序遍历,而它们分别对应于前缀表达式、中缀表达式、和后缀表达式。中缀表达式即我们平时所见到的表达式顺序,它虽然很容易被人所理解,但是计算机要解析中缀表达式却很困难,主要是因为中缀表达式带有括号,不容易进行处理。因此在计算表达式的值时,一般都是先把中缀表达式转化为后缀表达式,再利用栈进行求值。
这里我们选择先把中缀表达式转化为后缀表达式,然后进行后缀表达式求值。
例如中缀表达式为 a + b*c + (d * e + f) * g ,则转化为后缀表达式变为 a b c * + d e * f + g * + ,转化思路为:
1)如果遇到操作数,我们就直接将其添加到后缀表达式中。
2)如果遇到操作符,则我们将其放入到栈中,遇到左括号时我们也将其放入栈中。
3)如果遇到左括号直接入栈。
4)如果遇到一个右括号,则将栈元素弹出,将弹出的操作符添加到后缀表达式中直到遇到左括号为止,左括号只弹出而不添加到后缀表达式中。
5)如果遇到任何其他的操作符,如(“+”, “*”,“-”,“/”)等,从栈中弹出元素直到遇到更低优先级的元素(或者栈为空)为止。弹出完这些元素后,
才将遇到的操作符压入到栈中。
5)如果我们读到了输入的末尾,则将栈中所有元素依次弹出并添加到后缀表达式中。
由于JDK内置了Stack即栈内,因此我们并不需要自己手动编写栈,而是可以直接使用它。下面给出表达式转换的JAVA实现。
/*
* 将中缀表达式转换为后缀表达式
* @param str 需要转化的字符串
* @return 返回后缀表达式
* @exception 使用了未经初始化的变量
* */
private static List<String> changeForm(List<String> list) throws MyException{
//用来保存运算符号
Stack<String> symbolStack = new Stack<String>();
//用来存储数字及最后的后缀表达式
List<String> result = new ArrayList<String>();
//用来存储扫描到的字符串的下标位置
int index = 0;
while(index < list.size()){
String str = list.get(index);
//遇到整型数字直接添加到后缀表达式中
if(str.matches("[\\d]+")){
result.add(str);
}
//遇到浮点数直接添加到后缀表达式中
else if(str.matches("[\\d]+\\.[\\d]+")){
result.add(str);
}
//遇到变量名去变量名集合中查找有无该变量,有则入栈,无则报错
else if(str.matches("[a-zA-Z]+[a-zA-Z0-9]*")){
if (SentenceAnalysis.isKeyWord(str)) {
throw new MyException(Error.NAME_WITH_KEYWORD);
}
if (Varibles.variblesMap.containsKey(str)) {
String value = Varibles.variblesMap.get(str);
result.add(value);
}
//使用了未经初始化的变量进行运算
else{
throw new MyException(Error.NO_THIS_VARIBLE);
}
}
//+号
else if(str.equals("+")){
//符号栈为空则直接入栈
if(symbolStack.empty()){
symbolStack.push(str);
}else{
boolean mark = true;
while(mark && !symbolStack.empty()){
String top = symbolStack.peek();
if(top.equals("+")||top.equals("-")
||top.equals("*")||top.equals("/")
){
result.add(symbolStack.pop());
}else {
mark = false;
}
}
symbolStack.push(str);
}
}
//-号
else if(str.equals("-")){
//符号栈为空则直接入栈
if(symbolStack.empty()){
symbolStack.push(str);
}else{
boolean mark = true;
while(mark && !symbolStack.empty()){
String top = symbolStack.peek();
if(top.equals("+")||top.equals("-")
||top.equals("*")||top.equals("/")
){
result.add(symbolStack.pop());
}else {
mark = false;
}
}
symbolStack.push(str);
}
}
//*号
else if(str.equals("*")){
//符号栈为空则直接入栈
if(symbolStack.empty()){
symbolStack.push(str);
}else{
boolean mark = true;
while(mark && !symbolStack.empty()){
String top = symbolStack.peek();
if(top.equals("*") || top.equals("/")
){
result.add(symbolStack.pop());
}else {
mark = false;
}
}
symbolStack.push(str);
}
}
//除号
else if(str.equals("/")){
//符号栈为空则直接入栈
if(symbolStack.empty()){
symbolStack.push(str);
}else{
boolean mark = true;
while(mark && !symbolStack.empty()){
String top = symbolStack.peek();
if(top.equals("*")||top.equals("/")
){
result.add(symbolStack.pop());
}else {
mark = false;
}
}
symbolStack.push(str);
}
}
//左括号直接入栈
else if(str.equals("(")){
symbolStack.push(str);
}
//右括号
else if (str.equals(")")) {
boolean mark = true;
while(mark && !symbolStack.empty()){
//遇到左括号停止弹出栈顶元素,左括号不输出
if(symbolStack.peek().equals("(")){
symbolStack.pop();
mark = false;
}
//非左括号则弹出栈顶元素并将其加入后缀表达式
else {
result.add(symbolStack.pop());
}
}
}
//找不到该类型的符号
else {
throw new MyException(Error.NO_THIS_TYPE);
}
index++;
}
while(!symbolStack.empty()){
result.add(symbolStack.pop());
}
return result;
}
以上方法返回一个后缀表达式的字符串List,接下来我们要进行的是后缀表达式的求值,思路为:
1)从左到右扫描后缀表达式,若遇到操作数,则将其入栈。
2)若遇到操作符,则从栈中退出两个元素,先退出的放到运算符的右边,后退出的 放到运算符左边,运算后的结果再进栈。
3)表达式扫描完毕,此时,栈中仅有一个元素,即为运算的结果。
以上操作的第二步可能会出现栈中找不到元素的异常,第三步可能会出现栈中剩余元素不止一个的情况,这些都是因为输入表达式有误,应该报出相应错误。
以上操作的具体JAVA代码实现如下:
/*
* 后缀表达式求值
* @param list 后缀表达式
* @exception MyException 除数为0或者是表达式形式错误时都有可能抛出,具体情况参照异常提示信息
* @exception EmptyStackException 表达式形式错误
* */
private static String evaluation(List<String> list) throws MyException,EmptyStackException{
Stack<String> stack = new Stack<String>();
//扫描后缀表达式
for(int i = 0;i < list.size();i++){
String str = list.get(i);
//遇到操作数直接进栈
if(str.matches("[\\d]+") || str.matches("[\\d]+\\.[\\d]+")){
stack.push(str);
}else{
String snumber1 = stack.pop();
String snumber2 = stack.pop();
//全是整型.则都转换为整型
if(snumber1.matches("[\\d]+") && snumber2.matches("[\\d]+")){
int number1 = Integer.parseInt(snumber1);
int number2 = Integer.parseInt(snumber2);
switch (str) {
case "+":
stack.push(String.valueOf(number2 + number1));
break;
case "-":
stack.push(String.valueOf(number2 - number1));
break;
case "*":
stack.push(String.valueOf(number2 * number1));
break;
case "/":
if(number1 == 0){
throw new MyException(Error.DIVIDED_BY_ZERO);
}else{
stack.push(String.valueOf(number2 / number1));
}
break;
}
}
//有浮点型则都转换为浮点型
else{
double number1 = Double.parseDouble(snumber1);
double number2 = Double.parseDouble(snumber2);
switch (str) {
case "+":
stack.push(String.valueOf(number2 + number1));
break;
case "-":
stack.push(String.valueOf(number2 - number1));
break;
case "*":
stack.push(String.valueOf(number2 * number1));
break;
case "/":
if(number1 == 0){
throw new MyException(Error.DIVIDED_BY_ZERO);
}else{
stack.push(String.valueOf(number2 / number1));
}
break;
}
}
}
}
if(stack.size() > 1){
throw new MyException(Error.WRONG_FORMAT_OF_EXPRESSION);
}
return stack.pop();
}
由于项目要求里面有一条:计算结果的数据类型由输入表达式决定,因此我们在进行计算时应当考虑输入只有整型和包含浮点型两种情况。
最后我们需要为表达式计算类定义一个对外的计算接口,由他负责实现以上两个步骤,代码如下:
/*
* 对外的计算接口
* @param list 需要进行计算的字符串
* @return 返回计算结果
*/
public static String forResult(List<String> list) throws MyException,EmptyStackException{
List<String> result = changeForm(list);
return evaluation(result);
}
以上就是编译器的表达式计算部分,有了这一部分和上一篇文章所介绍的词法分析,我们接下来就可以来实现编译器的语法和语义分析部分了。
感谢阅读,下一篇文章再见!