// 實驗存檔html
運行截圖:spa
代碼中的整體轉化流程:中綴表達式字符串→tokens→逆波蘭tokens(即後綴表達式)→四元式。code
由後綴表達式寫出四元式很是容易,比較繁瑣的地方在於中綴轉逆波蘭,這裏採用的方法以下↓htm
經過維護一個符號棧(或者說運算符棧)來處理運算符間的優先級關係。從左至右讀入元素:blog
代碼:token
<!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>