上一篇文章介紹了我們這個編譯器的詞法分析部分,這一篇文章我們主要來討論如何用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);
}
以上就是編譯器的表達式計算部分,有了這一部分和上一篇文章所介紹的詞法分析,我們接下來就可以來實現編譯器的語法和語義分析部分了。
感謝閱讀,下一篇文章再見!