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

前言

這個編譯原理是一個系列,系列地址爲: https://blog.csdn.net/lpstudy/article/category/937055
考慮到很多小夥伴諮詢代碼的問題,現把鏈接發出來:https://github.com/lpstudy/compile
這個鏈接裏面具有這個系列所有的VS工程和代碼,工程是按照系列中的一個教程環境配置6來配置的,不過lib我好像沒有上傳到github。
如果大家發現任何問題,可以在github或者csdn,我有空的時候完善一下,爭取做到下載github工程即可跑。

簡介

本章講述的是編譯樹的實現,它包含樹節點,樹的構建,樹的遍歷三個部分。利用編譯樹,我們可以構建基本的運算節點以及數字節點,然後遍歷樹的過程就是執行算術運算的過程。
例如如下的一棵樹
這裏寫圖片描述
葉子節點和分支節點都是一個個的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個節點,並通過傳入的參數來確定節點的類型,值以及它們與孩子的對應關係,其代碼表達的樹就是上面圖中的那顆樹。最後設置根節點,然後分配內存,後序遍歷執行。

執行結果

這裏寫圖片描述

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章