編譯原理 #04# 中綴表達式轉化爲四元式(JavaScript實現)

// 實驗存檔html

運行截圖:spa

代碼中的整體轉化流程:中綴表達式字符串→tokens→逆波蘭tokens(即後綴表達式)→四元式。code

由後綴表達式寫出四元式很是容易,比較繁瑣的地方在於中綴轉逆波蘭,這裏採用的方法以下↓htm

經過維護一個符號棧(或者說運算符棧)來處理運算符間的優先級關係。從左至右讀入元素:blog

  1. 該元素是數字,則直接輸出該數字
  2. 該元素是算數運算符:
    1. 直接壓入符號棧的狀況:符號棧爲空,或者該運算符優先級大於棧頂運算符
    2. 不斷彈出(同時輸出該運算符)再壓入的狀況:符號棧不爲空,或者該運算符優先級小於等於棧頂運算符
  3. 該元素是左括號,則直接將左括號壓入符號棧,並賦予最小的優先級,避免被彈出。
  4. 該元素是右括號,則不斷彈出(同時輸出該運算符)符號棧中的元素,直到找到左括號,將左括號彈出但不輸出(後綴表達式中是沒有括號的)。
  5. 該元素是輸入終止符號,則彈出(同時輸出該運算符)符號棧中全部元素。

代碼: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>
相關文章
相關標籤/搜索