// 實驗存檔
運行截圖:

代碼中的總體轉化流程:中綴表達式字符串→tokens→逆波蘭tokens(即后綴表達式)→四元式。
由后綴表達式寫出四元式非常容易,比較繁瑣的地方在於中綴轉逆波蘭,這里采用的方法如下↓
通過維護一個符號棧(或者說運算符棧)來處理運算符間的優先級關系。從左至右讀入元素:
- 該元素是數字,則直接輸出該數字
- 該元素是算數運算符:
- 直接壓入符號棧的情況:符號棧為空,或者該運算符優先級大於棧頂運算符
- 不斷彈出(同時輸出該運算符)再壓入的情況:符號棧不為空,或者該運算符優先級小於等於棧頂運算符
- 該元素是左括號,則直接將左括號壓入符號棧,並賦予最小的優先級,避免被彈出。
- 該元素是右括號,則不斷彈出(同時輸出該運算符)符號棧中的元素,直到找到左括號,將左括號彈出但不輸出(后綴表達式中是沒有括號的)。
- 該元素是輸入終止符號,則彈出(同時輸出該運算符)符號棧中所有元素。
代碼:
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title></title>
</head>
<body>
<script>
let str = '4*(28+81*6-75)/8';
let tokens = tokenizer(str);
let inversePolishNotation = getInversePolishNotation(tokens);
let threeAddressCode = getThreeAddressCode(inversePolishNotation);
console.log("輸入:" + str);
console.log("逆波蘭式:" + inversePolishNotation.map(x => x.value));
console.log("四元式:" + threeAddressCode.map(x => x + '\n'));
// 獲取逆波蘭式相應的四元式
function getThreeAddressCode(inversePolishNotation) {
let result = [];
let stack = [];
let index = 0; // 臨時變量序號
for (let i = 0; i != inversePolishNotation.length; ++i) {
if (inversePolishNotation[i].tag == '數字') {
stack.push(inversePolishNotation[i]);
} else if (inversePolishNotation[i].tag == '算數運算符') {
let right = stack.pop(); // 右操作數應該是后入棧的那個
let left = stack.pop();
let temp = {
tag: '臨時變量',
value: 't' + index++,
};
stack.push(temp);
if (left && right) { // 如果左右操作數都不為空
result.push(`(${inversePolishNotation[i].value}, ${left.value}, ${right.value}, ${temp.value})`);
} else {
throw new Error("缺少操作數,非法運算!");
}
} else {
throw new Error("無法處理的token類型:" + tokens[i].tag);
}
}
return result;
}
// 輸入中綴形式的tokens,輸出逆波蘭形式的tokens
function getInversePolishNotation(tokens) {
let result = [];
let symbols = []; // 維護一個符號棧,以便處理運算符間的優先級關系
for (let i = 0; i != tokens.length; ++i) {
if (tokens[i].tag == '數字') {
result.push(tokens[i]);
} else if (tokens[i].tag == '算數運算符') {
if (symbols.length == 0 || symbols[symbols.length - 1].priority < tokens[i].priority) {
symbols.push(tokens[i]);
} else {
while (symbols.length != 0 && symbols[symbols.length - 1].priority >= tokens[i].priority) {
result.push(symbols.pop());
}
symbols.push(tokens[i]);
}
} else if (tokens[i].value == '(') {
symbols.push(tokens[i]);
} else if (tokens[i].value == ')') {
let find = false;
while (symbols.length != 0) {
let temp = symbols.pop();
if (temp.value == '(') {
find = true;
break;
} else {
result.push(temp);
}
}
if (!find) throw new Error("左括號缺失");
} else {
throw new Error("無法處理的token類型:" + tokens[i].tag);
}
}
while (symbols.length != 0) {
let temp = symbols.pop();
if (temp.value == '(') {
throw new Error("右括號缺失");
} else {
result.push(temp);
}
}
return result;
}
// 重用之前的詞法分析程序
function tokenizer(input) {
let s = input;
let cur = 0;
let peek = ' ';
let line = 1;
let readChar = () => s[cur++];
let undo = () => cur--;
let scan = () => { // 每次scan返回一個Token
// 略過空格,上次設置的peek值並不會被清空
for (;; peek = readChar()) {
if (peek == undefined) {
return null; // 讀完了
} else if (peek == ' ' || peek == '\t') {
continue; // 略過空格和Tab
} else if (peek == '\n') {
line++; // 記錄當前行
} else {
break;
}
}
if (/[0-9.]/.test(peek)) {
let temp = peek;
let hasPoint = false;
if (peek == '.') hasPoint = true;
while (/[0-9.]/.test(peek = readChar())) {
if (peek == '.' && hasPoint) {
console.log("第" + line + "行存在語法錯誤,數字中包含多個小數點");
return null;
} else if (peek == '.') {
hasPoint = true;
temp += peek;
} else {
temp += peek;
}
}
return {
tag: '數字',
value: Number(temp),
};
}
if (/[+*/-]/.test(peek)) {
let result = {
tag: '算數運算符',
value: peek,
};
if (peek == '+' || peek == '-') {
result.priority = 1; // 加減號的優先級較低
} else if (peek == '*' || peek == '/') {
result.priority = 2; // 乘除號的優先級較高
}
peek = ' ';
return result;
}
if (peek == '(') {
peek = ' ';
return {
tag: '括號',
value: '(',
priority: -99, // 左括號的優先級設置為最小,
// 不會因為除讀到右括號外的情況而出棧
};
}
if (peek == ')') {
peek = ' ';
return {
tag: '括號',
value: ')',
};
}
throw new Error("讀入非法字符: " + peek);
};
let tokens = [];
let token;
while (token = scan()) {
tokens.push(token);
}
return tokens;
}
</script>
</body>
</html>
