上篇文章記錄了乙個簡單的計算器。可是僅僅能計算乙個表示式,比方計算8+3*5,得到值23.這次在其基礎上加入了支援語句的功能,而且支援表示式中存在變數。比方以下:
num1 := 5;
num2 := num1+3*5;
num3 := num1 * (num2 - 20/5);
最後計算並返回的值是num3的值80.
依據這個樣例,能夠看出相比於上次那個簡單的計算器,加入的特性包括1、支援賦值語句 2、支援變數 3、支援多條賦值語句,也就是語句塊。
當中語句之間使用分號分隔,賦值符號為」:=」。變數名的規則和c語言規則一致。以字母或者下劃線開頭。能包括數字、字母、下劃線。
以下是新增的語法規則:
stmt -> id := expr;
stmts -> stmts stmt | stmt
id -> (letter|_) (letter | num)*
當中stmt代表一條語句,眼下的語句僅僅有一種。就是賦值語句。id代表乙個識別符號,在這裡僅代表變數,id的定義使用正規表示式定義,letter代表字母。
另外乙個值得關注的是新增了符號表,它是乙個結構體陣列,包括的結構體例如以下:
typedef struct sym
sym;
由於在這個簡單的計算器中,全部變數僅僅有一種值,即32位正數。
所以sym結構體的值val就是int。在我們的計算器中,變數能夠不用宣告而直接使用。假設事先沒有賦值的話。那麼變數的值將會是0。
符號表的填充是在生成了語法樹之後,對其進行求值時進行的。比方單條語句num1 := 5; 在生成語法樹
:= / \
num1 5
之後。開始呼叫calc函式對其遞迴求值。在對num1求值時,先檢查符號表中是否存在符號num1。假設不存在則將其增加到符號表,並對它賦值為0。最後對賦值操作符「:=」求值時,將其右邊表示式的值5賦給左邊識別符號num1,而且返回num1的值作為賦值操作符的返回值。
對於多條語句的求值,比方num1 := 5; num2 := num1 + 10; 生成的語法樹例如以下:
:= —> :=
/ \ / \
num1 5 num2 +
/ \
num1 10
可見對於兩條語句分別生成了兩棵語法樹。當中第二棵語法樹是作為第一棵語法樹的兄弟節點存在的,以下是樹節點的結構體treenode:
typedef struct treenode
attr;
union
val;/*屬性相應的值*/
}treenode;
當中兄弟節點的存在就是為了將多條語句連線起來,以便在語法分析和求值的時候方便找到。
以下看下在**中所做的相應改變,首先是在語法規則中新增的stmt和stmts規則所相應的兩個函式:
treenode *stmt()能夠看到,stmts函式中先呼叫stmt解析一條語句,也就是說,原始檔裡至少得有一條語句。接著是乙個while迴圈,不斷呼叫stmt函式進行語句的解析,直到檔案結束就退出while迴圈。此時返回一棵語法樹,該樹相應第一條語句,語法樹的兄弟節點指向它後面的語句。return node;
}以下是解析語句塊的函式stmts()
treenode *stmts()
return head;
}
接下來須要關注的就是calc求值函式,
/*計算語法樹的值*/int calc(treenode *node)
/*依據節點的屬性返回對應的值,眼下節點有兩種
屬性:數字或者操作符*/
switch (node->kind)
switch (node->val.tt)
break;
case idk:
s = findsym(node->val.name);
if (null == s)
else
break;
default:
printf("calc: unknown expression type.\n");
exit(1);
break;
}break;
case stmt:
/*賦值語句的計算*/能夠看到該函式有兩層的switch巢狀,第一層switch推斷節點的型別是語句還是表示式。第二層switch推斷其子型別,比方表示式能夠是常數、識別符號、數學表示式,語句眼下僅僅有賦值語句一種,將來能夠能有if語句、while語句等等。在計算計算賦值語句分支中,先計算賦值符號右邊表示式的值,這個值將會賦給左邊識別符號。而且作為賦值符號的值而返回。對於左邊識別符號,先推斷其是否在符號表中。假設不在就將其加入到符號表。並把右邊表示式的值賦給它,以便後面引用到該識別符號時使用。switch (node->attr.s)
else
break;
default:
printf("calc: unknown stmt kind.\n");
exit(1);
break;
}break; }
if (null != node->brother)
return val;
}
在計算switch的表示式識別符號的分支中,也是先查詢其是否在符號表中,假設在的話。就直接返回符號表中儲存的該識別符號的值。假設不在,那麼就將該識別符號加入到符號表,並初始化為0。
另外的一些改變是gettoken函式,加入了識別識別符號的分支。以下僅列出其加入的**:
default:/*首字元是字母或者下劃線*/
if (isalpha(c) || ('_' == c))
pushback();
token = id;
state = done;
}else
break;
加入的**是在switch的default分支中,假設取出的字元c是乙個字母或者下劃線。那麼說明是乙個識別符號的開始,然後while迴圈將識別符號從緩衝區中取出放到還有乙個儲存識別符號字串的小緩衝區中,直到遇到乙個非字母和下劃線的字元。
以下是向符號表加入符號和查詢符號的兩個函式:
void addsym(char *name, int val)symtbl[symidx] = (sym *)malloc(sizeof(sym));
symtbl[symidx]->name = name;
symtbl[symidx]->val = val;
symidx++;
}sym *findsym(char *name)
} return null;
}
這兩個函式比較簡單,就是用乙個陣列和乙個下標來管理,查詢使用順序查詢。簡直是簡單到不能忍了。只是眼下來說,還是夠用就好。夠用就好,呵呵。
好了,這個帶變數,帶語句版本號的計算器就完畢了。以下看看效果。
建立乙個測試檔案test.txt,輸入下面內容:
num1 := 5;
num2 := num1 + 10;
num3 := num2 * num1 - 20 / num1;
接下來編譯計算器
gcc -fno-builtin mycomplier.c -o mycpl
執行./mycpl test.txt
將會輸出 the result is 71.
看到這一條條帶變數的語句,還能計算出正確的結果,真是覺點有點像那麼回事 y(^_^)y。
email: [email protected]
有問題歡迎交流~~~~~
Linux下gcc編譯器和g 編譯器的那些事兒
使用c c 程式設計大約有三四個年頭了。最開始涉及到微控制器 嵌入式linux等,都使用的是c語言,那時主要寫linux驅動,甚至在arm板上寫linux應用程式時需要應用物件導向的思想的時候,都是使用c語言的結構體和函式指標來實現。當然,使用的編譯器自然就是gcc了。後來,慢慢的轉向了使用c 編寫...
gcc 編譯器和g 編譯器 幾個注意的問題
那麼在編譯的時候需要加入 lm 的選項,如果不加 lm 可能會報錯。gcc o test test.c lmgcc 編譯檔案字尾為.c的檔案時,編譯成編譯成c語言,編譯檔案字尾為.cpp的檔案時,編譯成c 語言。g 編譯.c cpp檔案時,只編譯成c 語言。gcc編譯字尾為.c的檔案有更少的預定義巨...
編譯原理 Lex和Bison實現計算器
實現以下步驟,掌握 flex 和 bison 的工作過程 a 在 dos 命令提示符下依次執行以下兩行命令 flex calc.lex bison ocalc.c calc.y b 編譯執行 calc.c 編譯執行完後 題目要求 用 flex 和 bison 實現乙個功能更為強大的計算器,包含以下運...