Demllie>編譯語言實現的核心問題,就是這裡不明白讓我白白寫6000行垃圾

Demllie發表於2021-01-05

前言

核心問題就是分析時的迭代,因為不懂怎麼迭代,我白白寫了好多垃圾。下面做一個計算器,弄明白這個東西。

Token定義


typedef enum  {
	NIL,
	ADD,
	SUB,
	MUL,
	DIV,
	MOD,
	NUM,
	LB,
	RB
}TokenType;


typedef union {
	char c;
	double v;
}Var;


class Token {
private:
	static int counter;//類變數,不能再此處初始化
	int num;
	TokenType type = TokenType::NIL;
	Var value;
public:
	Token(char _value) {
		num = counter++;
		switch (_value) {
		case '+':
			type = TokenType::ADD;
			break;
		case '-':
			type = TokenType::SUB;
			break;
		case '*':
			type = TokenType::MUL;
			break;
		case '/':
			type = TokenType::DIV;
			break;
		case '%':
			type = TokenType::MOD;
			break;
		case '(':
			type = TokenType::LB;
			break;
		case ')':
			type = TokenType::RB;
			break;
		default:
			cout << "違法字元>" << _value << endl;
			exit(1);
			break;
		}

		value.c = _value;
	}
	Token(double _value) {
		num = counter++;
		type = TokenType::NUM;
		value.v = _value;
	}


	TokenType GetTokenType()const {
		return type;
	}
	double GetTokenValue()const {
		if (type == TokenType::NUM)return value.v;
		return 0;
	}
};

int Token::counter = 0;//類變數初始化


主要是兩種,一種是單個char的操作符,另一種是多個字元的整數!!!

next找到下一個Token

定義幾個全域性變數,line陣列是讀取的一行字串,index是其中的索引,currentToken是當前的Token


static Token* currentToken=NULL;
static int index = 0;
static char line[256] = { 0 };

就是輪詢一邊,遇到空字元就跳過,遇到數字就結合起來,然後遇到非數字就把結合的整個數字變成一個Token。
遇到操作符就直接變成Token。


//獲取下一個token
void next() {

	char name[256] = { 0 };
	char c;
	double v=0;
	
	while ((c=line[index++]) != '\0') {
		if (c == ' ')continue;
		else if (c<='9'&&c>='0'   ) {
			v = v * 10 + c - '0';
			//
			char nextChar = line[index];
			if (!(nextChar <= '9' && nextChar >='0')) {
				if (currentToken != NULL) {
					delete currentToken;
					currentToken = NULL;
				}
				currentToken = new Token((double)v);
				break;
			}
		}
		else if (c == '(' || c==')' ) {
			if (currentToken != NULL)
				delete currentToken;
			currentToken = new Token((char)c);
			break;
		}
		else if (c == '+' || c == '-' || c == '*' || c == '\\' || c == '%'||c=='/') {
			if (currentToken != NULL) {
				delete currentToken;
				currentToken = NULL;
			}
			currentToken = new Token((char)c);
			break;
		}
	}


}

factor

factor、term、expr是核心問題。

expr是加減表示式或者一個數,形式是

expr ::= term [ ('+'|'-') term ]

term是乘除取餘表示式或者一個數,形式是

term ::= factor [ ('*'|'/'|'%') factor]

factor是元素或者括號表示式,形式是

factor::= NUM | '(' expr ')'



#define ENUM_TYPE(type)   (type==0?"NIL":  \
												  (type==1?"ADD": \
											      (type==2?"SUB": \
												  (type==3?"MUL": \
												  (type==4?"DIV":  \
												  (type==5?"MOD": \
												  (type==6?"NUM": \
												  (type==7?"LB":"RB"))))))))


//終結符,是最底層的元素
double factor() {

	double value = 0;
	if (currentToken->GetTokenType() == TokenType::NUM) {
		value = currentToken->GetTokenValue();
	}
	else if (currentToken->GetTokenType() == TokenType::LB) {
		next();
		double ans = expr();//獲取括號內的answer


		//錯誤處理
		if (currentToken->GetTokenType() != TokenType::RB) {
			cout << "語法分析出錯,右括號不匹配!";
			if (currentToken->GetTokenType() == TokenType::NUM){
				cout << "value > "<<currentToken->GetTokenValue() << endl;
			}
			else {
				cout << "value > " << ENUM_TYPE(currentToken->GetTokenType()) << endl;
			}
		}

		value = ans;//終結符為表示式asnwer
		
	}

	next();//離開的時候別忘了移動一下token
	cout << "factor>" << value << endl;
	return value;
}

term


//乘除取餘表示式
double term() {
	
	double value = 0;
	double left = factor();//乘除的上一級是單個元素或者表達
	double right = 0;
	value = left;


	//暫時儲存current token前兩個token的型別和值,即前一個OP
	Token tempToken = *currentToken;


	

	while (tempToken.GetTokenType() == TokenType::MOD || tempToken.GetTokenType() == TokenType::MUL  || tempToken.GetTokenType() == TokenType::DIV) {
		next();

		right = factor();//乘除的上一級是單個元素或者表達
		switch (tempToken.GetTokenType() ) {
		case TokenType::MOD:
			value = (int)value % (int)right;
			break;
		case TokenType::MUL:
			value = value * right;
			break;
		case TokenType::DIV:
			if (right == 0) {
				cout << "除數不能為零"<< endl;
				exit(1);
				return -1;
			}
			value = value / right;
			break;
		}
		tempToken = *currentToken;//移動token

	}
	cout << "term>" << value << endl;
	return value;
}

expr

和term是類似的。


//加減表示式
double expr() {

	double value = 0;
	double left = term();//加減的上一級是乘除取餘
	double right = 0;
	value = left;


	//暫時儲存current token前兩個token的型別和值,即前一個OP
	Token tempToken = *currentToken;

	

	while (tempToken.GetTokenType() == TokenType::ADD || tempToken.GetTokenType() == TokenType::SUB ) {
		next();

		right = term(); //加減的上一級是乘除取餘
		switch (tempToken.GetTokenType()) {
		case TokenType::ADD:
			value = value + right;
			break;
		case TokenType::SUB:
			value = value - right;
			break;
		}
		tempToken = *currentToken;//移動token

	}
	cout << "expr>" << value << endl;
	return value;
}

從檔案中讀取字串


int main(void) {

	FILE* file = NULL;
	int a = fopen_s(&file, "test.txt", "r");
	if (a != 0) {
		cout << "檔案打不開!" << endl;
		return 1;
	}

	fgets(line, 256, file);
	cout << line << endl;
	next();//取得第一個token
	cout << "ans="<<expr()<< endl;//迭代下去,返回answer
	

	return 0;
}

建一個文字檔案test.txt,輸入公式執行結果如下:

1+2*3/4 + (1+3*4)*4*5/2*12 - 0 - 100 + 120*2

factor>1
term>1
factor>2
factor>3
factor>4
term>1.5
factor>1
term>1
factor>3
factor>4
term>12
expr>13
factor>13
factor>4
factor>5
factor>2
factor>12
term>1560
factor>0
term>0
factor>100
term>100
factor>120
factor>2
term>240
expr>1702.5
ans=1702.5

相關文章