1. 程式人生 > >小白說編譯原理-7-算術表示式編譯樹(支撐類)

小白說編譯原理-7-算術表示式編譯樹(支撐類)

簡介

本章講述的是編譯樹的實現,它包含樹節點,樹的構建,樹的遍歷三個部分。利用編譯樹,我們可以構建基本的運算節點以及數字節點,然後遍歷樹的過程就是執行算術運算的過程。
例如如下的一棵樹
這裡寫圖片描述
葉子節點和分支節點都是一個個的Node,它具有不同的型別(運算子和數字)。

程式碼如下

#include <iostream>
#include <malloc.h>
using namespace std;

#define  MAX_CHILDREN 4
int my_mem[100];            // “記憶體”
int offset;

enum                    // 結點型別——kind
{ STMT_NODE = 0, EXPR_NODE, DECL_NODE }; enum // 語句結點子型別——kindkind { IF_STMT = 0, WHILE_STMT, INPUT_STMT, PRINT_STMT, COMP_STMT }; enum // 表示式結點子型別——kindkind { TYPE_EXPR = 0, OP_EXPR, NOT_EXPR, ARRAY_EXPR, CONST_EXPR, ID_EXPR }; enum
// 宣告結點子型別——kindkind { VAR_DECL = 0, ARRAY_DECL }; enum // 運算——op { PLUS = 0, MINUS }; enum { Integer = 0, }; union NodeAttr { int op; // 表示式結點,子型別是運算型別時,用op儲存具體運算 int vali; // 表示式結點,常量表達式時,用vali儲存整型常量值 char valc; // 字元值
NodeAttr(void) { op = 0; } // 幾種建構函式 NodeAttr(int i) { op = i; } NodeAttr(char c) { valc = c; } }; struct Node { struct Node *children[MAX_CHILDREN]; // 孩子結點 int kind; // 結點型別 int kind_kind; // 子型別 NodeAttr attr; // 結點屬性 int addr; // 分配的記憶體空間(陣列下標) }; class tree // 語法樹類 { private: Node *root; // 根結點 private: void recursive_get_addr(Node *t); // 為臨時變數(如表示式)分配儲存空間 void recursive_execute(Node *t); // 遍歷樹,執行源程式 public: void setRoot(Node* p){root = p;} Node *NewRoot(int kind, int kind_kind, NodeAttr attr, int type, Node *child1 = NULL, Node *child2 = NULL, Node *child3 = NULL, Node *child4 = NULL); // 建立一個結點,設定其屬性,連線孩子結點 void get_addr(void); // 分配空間和執行程式碼的介面 void execute(void); }; Node * tree::NewRoot(int kind, int kind_kind, NodeAttr attr, int type, Node *child1, Node *child2, Node *child3 , Node *child4) { Node* node = new Node(); node->kind = kind; node->kind_kind = kind_kind; node->attr = attr; node->children[0] = child1; node->children[1] = child2; node->children[2] = child3; node->children[3] = child4; return node; } void tree::get_addr(void) { cout << "allocate memory..." << endl; offset = 0; recursive_get_addr(root); // 介面函式直接呼叫實際分配空間的遞迴函式 } void tree::recursive_get_addr(Node *t) { if (t) { // 空指標什麼也不做 if (t->kind == EXPR_NODE) { // 為表示式結點分配儲存空間 t->addr = offset++; //cout << t->addr << endl; } for (int i = 0; i < MAX_CHILDREN; i++) // 遞迴處理所有子樹——先序遍歷 recursive_get_addr(t->children[i]); } } void tree::execute(void) { cout << "execute..." << endl; recursive_execute(root); // 介面函式呼叫遞迴函式 cout << my_mem[root->addr] << endl; // 從記憶體取出執行結果,輸出 } void tree::recursive_execute(Node *t) { if (t) { for (int i = 0; i < MAX_CHILDREN; i++) // 後序遍歷 recursive_execute(t->children[i]); if (t->kind == EXPR_NODE) // 表示式結點 if (t->kind_kind == OP_EXPR) { // 運算型別表示式 if (t->attr.op == PLUS) // 加法表示式 // 從記憶體(my_mem)中取出兩個孩子的值,進行加法,結果寫回記憶體 my_mem[t->addr] = my_mem[t->children[0]->addr] + my_mem[t->children[1]->addr]; else if (t->attr.op == MINUS) // 減法的處理類似加法 my_mem[t->addr] = my_mem[t->children[0]->addr] - my_mem[t->children[1]->addr]; } else if (t->kind_kind == CONST_EXPR) // 常量表達式,將值(在vali中)儲存至分配的記憶體中 my_mem[t->addr] = t->attr.vali; } } int main(int argc, char *argv[]) { tree expr; Node *p, *q, *r; // 建立結點9 p = expr.NewRoot(EXPR_NODE, CONST_EXPR, NodeAttr(9), Integer); // 建立結點5 q = expr.NewRoot(EXPR_NODE, CONST_EXPR, NodeAttr(5), Integer); // 建立減法結點,孩子結點為9和5 r = expr.NewRoot(EXPR_NODE, OP_EXPR, NodeAttr(MINUS), Integer, p, q); q = expr.NewRoot(EXPR_NODE, CONST_EXPR, NodeAttr(2), Integer); p = expr.NewRoot(EXPR_NODE, OP_EXPR, NodeAttr(PLUS), Integer, r, q); expr.setRoot(r); expr.get_addr(); // 為(子)表示式(們)分配儲存空間 expr.execute(); // 執行程式碼 }

程式碼解釋

節點型別: 句子節點,表示式節點和變數定義節點。
節點型別的子型別,只說明句子,包含If語句,while語句,輸入輸出語句等等。
struct Node: 表示樹中的一個節點,它有多個孩子節點,以及節點的型別,節點儲存資料的地址和節點屬性
class tree: 表示一顆語法樹,包含樹的遍歷方法和分配記憶體的方法。

recursive_execute: 樹的遍歷方法

if (t->kind_kind == OP_EXPR) {      // 運算型別表示式
    if (t->attr.op == PLUS)         // 加法表示式
        // 從記憶體(my_mem)中取出兩個孩子的值,進行加法,結果寫回記憶體
        my_mem[t->addr] = my_mem[t->children[0]->addr] + my_mem[t->children[1]->addr]; 
    else if (t->attr.op == MINUS)   // 減法的處理類似加法
        my_mem[t->addr] = my_mem[t->children[0]->addr] - my_mem[t->children[1]->addr];
}
else if (t->kind_kind == CONST_EXPR)    // 常量表達式,將值(在vali中)儲存至分配的記憶體中
    my_mem[t->addr] = t->attr.vali;

首先對數的所有孩子進行遍歷執行,得到它的孩子的執行結果。
然後檢視當前節點的型別,如果當前是表示式型別,且是加法,那麼就將兩個孩子的資料相加,每個孩子有一個addr屬性儲存它對應的地址值。如果節點是CONST資料型別,那麼直接將節點對應地址的內容設定為對應的資料。

recursive_get_addr: 分配記憶體的方法

void tree::recursive_get_addr(Node *t)
{
    if (t) {        // 空指標什麼也不做
        if (t->kind == EXPR_NODE) { // 為表示式結點分配儲存空間
            t->addr = offset++;
            //cout << t->addr << endl;
        }
        for (int i = 0; i < MAX_CHILDREN; i++)  // 遞迴處理所有子樹——先序遍歷
            recursive_get_addr(t->children[i]);
    }
}

上述函式遞迴給表示式節點分配記憶體,這是因為語句節點並不具有值的概念,只有表示式節點才有值,才需要分配記憶體以儲存執行結果。

main:構造表示式樹,並執行

int main(int argc, char *argv[])
{
    tree expr;
    Node *p, *q, *r;

    // 建立結點9
    p = expr.NewRoot(EXPR_NODE, CONST_EXPR, NodeAttr(9), Integer);
    // 建立結點5
    q = expr.NewRoot(EXPR_NODE, CONST_EXPR, NodeAttr(5), Integer);
    // 建立減法結點,孩子結點為9和5
    r = expr.NewRoot(EXPR_NODE, OP_EXPR, NodeAttr(MINUS), Integer, p, q);
    q = expr.NewRoot(EXPR_NODE, CONST_EXPR, NodeAttr(2), Integer);
    p = expr.NewRoot(EXPR_NODE, OP_EXPR, NodeAttr(PLUS), Integer, r, q);
    expr.setRoot(r);
    expr.get_addr();    // 為(子)表示式(們)分配儲存空間
    expr.execute(); // 執行程式碼
}

上述程式碼建立5個節點,並通過傳入的引數來確定節點的型別,值以及它們與孩子的對應關係,其程式碼表達的樹就是上面圖中的那顆樹。最後設定根節點,然後分配記憶體,後序遍歷執行。

執行結果

這裡寫圖片描述