運用Yacc和Lex來DIY計算器(程式碼部分)
阿新 • • 發佈:2021-01-16
前言
這篇文章有點makefile的感覺
直接看程式碼應該問題不大,這裡直接附上程式碼,本來想弄在GitHub上,後來覺得太麻煩了,還是直接複製黏貼吧。畢竟這是個小工程,如果大家有更好的想法或者在跑程式碼的時候有什麼疑惑歡迎留言啊!!
介紹
我們的程式碼支援識別if-else, while, {}, (), sin, 二進位制轉十進位制(B2D), 計算階乘(Factorial), 基礎四則運算等簡單功能。可以通過compiler.c做出虛擬碼組合語言版本,graph.c可以畫出我們程式碼的syntax tree,interpreter.c是直接給出執行的結果的程式碼。
程式碼檔案概覽
- calc3.h
- LA.l
- SA.y
- compiler.c
- graph.c
- interpreter.c
- text.c
編譯方式
因為我們用到了C++裡面的map,所以這裡我們都是用g++編譯的,會有很多warnings,但沒有什麼大問題(不出意外的話。。)
#!/bin/sh
flex LA.l
yacc -dtv SA.y
# 編譯interpreter.c
g++ -c interpreter.c
g++ -c lex.yy.c
g++ -c y.tab.c
g++ -o a_i.out y.tab.o lex.yy.o interpreter.o
# 編譯compiler.c
g++ -c compiler.c
g++ -c lex.yy.c
g++ -c y.tab.c
g++ -o a_c.out y.tab.o lex.yy.o compiler.o
# 編譯graph.c
g++ -c graph.c
g++ -c lex.yy.c
g++ -c y.tab.c
g++ -o a_g.out y.tab.o lex.yy.o graph.o
# 執行測試程式
echo --------------------------Test File------------------------------------------
cat test.txt
echo --------------------------Output of Interpreter------------------------------
./a_i.out < test.txt
echo --------------------------Output of Compiler---------------------------------
./a_c.out < test.txt
echo --------------------------Output of Graph------------------------------------
./a_g.out < test.txt
程式碼部分
calc3.h
#include <map>
#include <utility>
#include <string.h>
typedef enum { typeCon, typeId, typeOpr, typeStr} nodeEnum;
/* constants */
typedef struct {
float value; /* value of constant */
} conNodeType;
typedef struct {
char* value; /* value of constant */
} strNodeType;
/* identifiers */
typedef struct {
char* idName; /* subscript to sym array */
} idNodeType;
/* operators */
typedef struct {
int oper; /* operator */
int nops; /* number of operands */
struct nodeTypeTag *op[1]; /* operands, extended at runtime */
} oprNodeType;
typedef struct nodeTypeTag {
nodeEnum type; /* type of node */
union {
conNodeType con; /* constants */
strNodeType str; /* string */
idNodeType id; /* identifiers */
oprNodeType opr; /* operators */
};
} nodeType;
struct cmp_str /* 自定義函式,用來判斷兩個str是否是一樣的,用來輔助map*/
{
bool operator()(char const *a, char const *b) const
{
return strcmp(a, b) < 0;
}
};
extern std:: map<char *, float, cmp_str> sym;
LA.l
%{
#include <stdlib.h>
#include "calc3.h"
#include "y.tab.h"
#include <string>
void yyerror(char *);
%}
%%
0 {yylval.iValue = atoi(yytext); return FLOAT;}
[0-9]+(\.[0-9]+)?([eE][0-9]+)? {yylval.iValue = atof(yytext); return FLOAT;}
[01]+B {yylval.s = (char*)malloc(sizeof(yytext)); strcpy(yylval.s,yytext); return BIN;}
"sin" {return SIN;}
"cos" {return COS;}
"pi" {return PI;}
"fac" {return FAC;}
"B2D" {return B2D;}
[-()<>=+*/;{}.^] { return *yytext;}
">=" return GE;
"<=" return LE;
"==" return EQ;
"!=" return NE;
"while" return WHILE;
"if" return IF;
"else" return ELSE;
"print" return PRINT;
[_[:alpha:]][_[:alnum:]]* {yylval.s = (char*)malloc(sizeof(yytext)); strcpy(yylval.s,yytext); return VARIABLE;}
[ \t\n]+ ;
. yyerror("Unknown character");
%%
int yywrap(void) {
return 1;
}
SA.y
%{
#include <stdio.h>
#include <stdlib.h>
#include <stdarg.h>
#include <math.h>
#include "calc3.h"
#define _USE_MATH_DEFINES
/* prototypes */
nodeType *opr(int oper, int nops, ...);
nodeType *id(char* str);
nodeType *con(float value);
nodeType *str(char* value);
void freeNode(nodeType *p);
int ex(nodeType *p);
int yylex(void);
void yyerror(char *s);
std::map<char *, float, cmp_str> sym;
%}
%union {
float iValue;
char sIndex;
nodeType *nPtr;
char* s;
};
%token <iValue> FLOAT
%token <s> VARIABLE
%token <s> BIN
%token PI
%token WHILE IF PRINT SIN COS B2D FAC
%nonassoc IFX
%nonassoc ELSE
%left GE LE EQ NE '>' '<'
%left '+' '-'
%left '*' '/'
%nonassoc UMINUS
%left '^'
%type <nPtr> stmt expr stmt_list
%%
program:
function { exit(0); }
;
function:
function stmt {ex($2); freeNode($2);}
|
;
stmt:
';' { $$ = opr(';', 2, NULL, NULL); }
| expr ';' { $$ = $1; }
| PRINT expr ';' { $$ = opr(PRINT, 1, $2); }
| VARIABLE '=' expr ';' { $$ = opr('=', 2, id($1), $3); }
| WHILE '(' expr ')' stmt { $$ = opr(WHILE, 2, $3, $5); }
| IF '(' expr ')' stmt %prec IFX { $$ = opr(IF, 2, $3, $5); }
| IF '(' expr ')' stmt ELSE stmt { $$ = opr(IF, 3, $3, $5, $7); }
| '{' stmt_list '}' { $$ = $2; }
;
stmt_list:
stmt { $$ = $1; }
| stmt_list stmt { $$ = opr(';', 2, $1, $2); }
;
expr:
FLOAT { $$ = con($1); }
| BIN { $$ = str($1); }
| VARIABLE { $$ = id($1);}
| '-' expr %prec UMINUS { $$ = opr(UMINUS, 1, $2); }
| expr '+' expr { $$ = opr('+', 2, $1, $3); }
| expr '-' expr { $$ = opr('-', 2, $1, $3); }
| expr '*' expr { $$ = opr('*', 2, $1, $3); }
| expr '/' expr { $$ = opr('/', 2, $1, $3); }
| expr '<' expr { $$ = opr('<', 2, $1, $3); }
| expr '>' expr { $$ = opr('>', 2, $1, $3); }
| expr '^' expr { $$ = opr('^', 2, $1, $3); }
| expr GE expr { $$ = opr(GE, 2, $1, $3); }
| expr LE expr { $$ = opr(LE, 2, $1, $3); }
| expr NE expr { $$ = opr(NE, 2, $1, $3); }
| expr EQ expr { $$ = opr(EQ, 2, $1, $3); }
| '(' expr ')' { $$ = $2; }
| SIN '(' expr ')' { $$ = opr(SIN, 1, $3 ); }
| COS '(' expr ')' { $$ = opr(COS, 1, $3 ); }
| PI { $$ = con(M_PI);}
| FAC '(' expr ')' { $$ = opr(FAC,1,$3); }
| B2D '(' expr ')' { $$ = opr(B2D,1,$3); }
;
%%
#define SIZEOF_NODETYPE ((char *)&p->con - (char *)p)
nodeType *con(float value) { nodeType *p;
/* allocate node */
if ((p = (nodeType*)malloc(sizeof(nodeType))) == NULL) yyerror("out of memory");
/* copy information */ p->type = typeCon; p->con.value = value;
return p;
}
nodeType *str(char* value) { nodeType *p;
/* allocate node */
if ((p = (nodeType*)malloc(sizeof(nodeType))) == NULL) yyerror("out of memory");
/* copy information */ p->type = typeStr; p->str.value = value;
return p;
}
nodeType *id(char* str) { nodeType *p;
/* allocate node */
if ((p = (nodeType*)malloc(sizeof(nodeType))) == NULL) yyerror("out of memory");
/* copy information */ p->type = typeId; p->id.idName = str;
return p;
}
nodeType *opr(int oper, int nops, ...) {
va_list ap;
nodeType *p;
int i;
/* allocate node, extending op array */
if ((p = (nodeType*)malloc(sizeof(nodeType) + (nops-1) * sizeof(nodeType *))) == NULL)
yyerror("out of memory");
/* copy information */
p->type = typeOpr;
p->opr.oper = oper;
p->opr.nops = nops;
va_start(ap, nops);
for (i = 0; i < nops; i++)
p->opr.op[i] = va_arg(ap, nodeType*);
va_end(ap);
return p;
}
void freeNode(nodeType *p) {
int i;
if (!p) return;
if (p->type == typeOpr) {
for (i = 0; i < p->opr.nops; i++)
freeNode(p->opr.op[i]);
}
free (p);
}
void yyerror(char *s) {
fprintf(stdout, "%s\n", s);
}
int main(void) {
yyparse();
return 0;
}
compiler.c
#include <stdio.h>
#include "calc3.h"
#include "y.tab.h"
static int lbl;
int ex(nodeType *p) {
int lbl1, lbl2;
if (!p)
return 0;
switch(p->type) {
case typeCon:
printf("\tpush\t%.4f\n", p->con.value);
break;
case typeId:
printf("\tpush\t%s\n", p->id.idName);
break;
case typeStr:
printf("\tpush\t%s\n", p->str.value);
break;
case typeOpr:
switch(p->opr.oper) {
case WHILE:
printf("L%03d:\n", lbl1 = lbl++);
ex(p->opr.op[0]);
printf("\tjz\tL%03d\n", lbl2 = lbl++);
ex(p->opr.op[1]);
printf("\tjmp\tL%03d\n", lbl1);
printf("L%03d:\n", lbl2);
break;
case IF:
ex(p->opr.op[0]);
if (p->opr.nops > 2) {
/* if else */
printf("\tjz\tL%03d\n", lbl1 = lbl++);
ex(p->opr.op[1]);
printf("\tjmp\tL%03d\n", lbl2 = lbl++);
printf("L%03d:\n", lbl1);
ex(p->opr.op[2]);
printf("L%03d:\n", lbl2);
} else {
/* if */
printf("\tjz\tL%03d\n", lbl1 = lbl++);
ex(p->opr.op[1]);
printf("L%03d:\n", lbl1);
}
break;
case PRINT:
ex(p->opr.op[0]);
printf("\tprint\n");
break;
case '=':
ex(p->opr.op[1]);
printf("\tpop\t%s\n", p->opr.op[0]->id.idName);
break;
case UMINUS:
ex(p->opr.op[0]);
printf("\tneg\n");
break;
case FAC:
ex(p->opr.op[0]);
printf("\tfac\n");
break;
case SIN:
ex(p->opr.op[0]);
printf("\tsin\n");
break;
case COS:
ex(p->opr.op[0]);
printf("\tcos\n");
break;
case B2D:
ex(p->opr.op[0]);
printf("\tb2d\n");
break;
default:
ex(p->opr.op[0]);
ex(p->opr.op[1]);
switch(p->opr.oper) {
case '+': printf("\tadd\n");
break;
case '-': printf("\tsub\n");
break;
case '*': printf("\tmul\n");
break;
case '/': printf("\tdiv\n");
break;
case '<': printf("\tcompLT\n");
break;
case '>': printf("\tcompGT\n");
break;
case '^': printf("\tpow\n");
break;
case GE: printf("\tcompGE\n");
break;
case LE: printf("\tcompLE\n");
break;
case NE: printf("\tcompNE\n");
break;
case EQ: printf("\tcompEQ\n");
break;
}
}
}
return 0;
}
graph.c
#include <stdio.h>
#include <stdarg.h>
#include <stdlib.h>
#include <string.h>
#include "calc3.h"
#include "y.tab.h"
int del = 1; /* distance of graph columns */ int eps = 3; /* distance of graph lines */
/* interface for drawing (can be replaced by "real" graphic using GD or other) */
void graphInit (void); void graphFinish();
void graphBox (char *s, int *w, int *h);
void graphDrawBox (char *s, int c, int l);
void graphDrawArrow (int c1, int l1, int c2, int l2);
/* recursive drawing of the syntax tree */
void exNode (nodeType *p, int c, int l, int *ce, int *cm);
/***********************************************************/
/* main entry point of the manipulation of the syntax tree */
int ex (nodeType *p) {
int rte, rtm;
graphInit ();
exNode (p, 0, 0, &rte, &rtm);
graphFinish();
return 0;
}
/*c----cm---ce----> drawing of leaf-nodes l leaf-info
*/
/*c---------------cm--------------ce----> drawing of non-leaf-nodes l node-info
* |
* ------------- ...---- * | | | * v v v
* child1 child2 ... child-n
* che che che
*cs cs cs cs
*
*/
void exNode(nodeType*p,
int c, int l,/* start column and line of node */
int *ce, int *cm /* resulting end column and mid of node */
)
{
int w, h; /* node width and height */
char *s; /* node text */
int cbar; /* "real" start column of node (centred above subnodes)*/
int k; /* child number */
int che, chm; /* end column and mid of children */
int cs; /* start column of children */
char word[20]; /* extended node text */
if (!p) return;
strcpy (word, "???"); /* should never appear */
s = word;
switch(p->type) {
case typeCon:
sprintf (word, "c(%.4f)", p->con.value);
break;
case typeStr:
sprintf (word, "str(%s)", p->str.value);
break;
case typeId:
sprintf (word, "id(%s)", p->id.idName);
break;
case typeOpr:
switch(p->opr.oper){
case WHILE:
s = "while";
break;
case IF:
s = "if";
break;
case ELSE:
s = "else";
break;
case PRINT:
s = "print";
break;
case ';':
s = "[;]";
break;
case '=':
s = "[=]";
break;
case UMINUS:
s = "[_]";
break;
case '+':
s = "[+]";
break;
case '-':
s = "[-]";
break;
case '*':
s = "[*]";
break;
case '/':
s = "[/]";
break;
case '<':
s = "[<]";
break;
case '>':
s = "[>]";
break;
case '^':
s = "[^]";
break;
case GE:
s = "[>=]";
break;
case LE:
s = "[<=]";
break;
case NE:
s = "[!=]";
break;
case EQ:
s = "[==]";
break;
case SIN:
s = "sin";
break;
case COS:
s = "cos";
break;
case FAC:
s = "fac";
break;
case B2D:
s = "b2d";
break;
}
break;
}
/* construct node text box */
graphBox (s, &w, &h);
cbar = c;
*ce = c + w;
*cm = c + w / 2;
/* node is leaf */
if (p->type == typeCon ||p->type == typeStr || p->type == typeId || p->opr.nops == 0) {
graphDrawBox (s, cbar, l);
return;
}
/* nodezhas children*/
cs = c;
for (k = 0; k < p->opr.nops; k++) {
exNode (p->opr.op[k], cs, l+h+eps, &che, &chm);
cs = che;
}
/* total node width */
if (w < che - c) {
cbar += (che - c - w) / 2;
*ce = che;
*cm = (c + che) / 2;
}
/* draw node */
graphDrawBox (s, cbar, l);
/* draw arrows (not optimal: children are drawn a second time) */
cs = c;
for (k = 0; k < p->opr.nops; k++) {
exNode (p->opr.op[k], cs, l+h+eps, &che, &chm);
graphDrawArrow (*cm, l+h, chm, l+h+eps-1);
cs = che;
}
}
/* interface for drawing */
#define lmax 200
#define cmax 200
char graph[lmax][cmax]; /* array for ASCII-Graphic */
int graphNumber = 0;
void graphTest (int l, int c)
{ int ok;
ok = 1;
if (l < 0) ok = 0;
if (l >= lmax) ok = 0;
if (c < 0) ok = 0;
if (c >= cmax) ok = 0;
if (ok) return;
printf ("\n+++error: l=%d, c=%d not in drawing rectangle 0, 0 ... %d,%d",l, c, lmax, cmax);
exit (1);
}
void graphInit (void) {
int i, j;
for (i = 0; i < lmax; i++)
for (j = 0; j < cmax; j++)
graph[i][j] = ' ';
}
void graphFinish() {
int i, j;
for (i = 0; i < lmax; i++) {
for (j = cmax-1; j > 0 && graph[i][j] == ' '; j--);
graph[i][cmax-1] = 0;
if (j < cmax-1)
graph[i][j+1] = 0;
if (graph[i][j] == ' ')
graph[i][j] = 0;
}
for (i = lmax-1; i > 0 && graph[i][0] == 0; i--);
printf ("\n\nGraph %d:\n", graphNumber++);
for (j = 0; j <= i; j++)
printf ("\n%s", graph[j]);
printf("\n");
}
void graphBox (char *s, int *w, int *h) {
*w = strlen (s) + del;
*h = 1;
}
void graphDrawBox (char *s, int c, int l) {
int i;
graphTest(l, c+strlen(s)-1+del);
for (i = 0; i < strlen (s); i++) {
graph[l][c+i+del] = s[i];
}
}
void graphDrawArrow (int c1, int l1, int c2, int l2) {
int m;
graphTest (l1, c1);
graphTest (l2, c2);
m = (l1 + l2) / 2;
while (l1 != m) {
graph[l1][c1] = '|'; if (l1 < l2) l1++; else l1--;
}
while (c1 != c2) {
graph[l1][c1] = '-'; if (c1 < c2) c1++; else c1--;
}
while (l1 != l2) {
graph[l1][c1] = '|'; if (l1 < l2) l1++; else l1--;
}
graph[l1][c1] = '|';
}
interpreter.c
#include <stdio.h>
#include "calc3.h"
#include "y.tab.h"
#include "math.h"
float factorial(float fac);
float BtoD(char* bin);
float res;
int count = 0;
std::map<char *, float, cmp_str>::iterator it;
float ex(nodeType *p) {
if (!p) return 0;
switch(p->type) {
case typeCon:
return p->con.value;
case typeStr:
return 0; //meaningless
case typeId:
return sym[p->id.idName];
case typeOpr:
switch(p->opr.oper) {
case WHILE:
while(ex(p->opr.op[0]))
ex(p->opr.op[1]);
return 0;
case IF:
if (ex(p->opr.op[0]))
ex(p->opr.op[1]);
else if (p->opr.nops > 2)
ex(p->opr.op[2]);
return 0;
case PRINT:
printf("%.4f\n", ex(p->opr.op[0]));
return 0;
case ';':
ex(p->opr.op[0]); return ex(p->opr.op[1]);
case '=':
it = sym.find(p->opr.op[0]->id.idName);
if(it!=sym.end()){
sym[p->opr.op[0]->id.idName] = ex(p->opr.op[1]);
}
else
{
sym.insert(std::pair<char*, float>(p->opr.op[0]->id.idName, ex(p->opr.op[1])));
}
return ex(p->opr.op[1]);
case UMINUS: return -ex(p->opr.op[0]);
case '+': return ex(p->opr.op[0]) + ex(p->opr.op[1]);
case '-': return ex(p->opr.op[0]) - ex(p->opr.op[1]);
case '*': return ex(p->opr.op[0]) * ex(p->opr.op[1]);
case '/': return ex(p->opr.op[0]) / ex(p->opr.op[1]);
case '<': return ex(p->opr.op[0]) < ex(p->opr.op[1]);
case '>': return ex(p->opr.op[0]) > ex(p->opr.op[1]);
case '^': return pow(ex(p->opr.op[0]), ex(p->opr.op[1]));
case GE: return ex(p->opr.op[0]) >= ex(p->opr.op[1]);
case LE: return ex(p->opr.op[0]) <= ex(p->opr.op[1]);
case NE: return ex(p->opr.op[0]) != ex(p->opr.op[1]);
case EQ: return ex(p->opr.op[0]) == ex(p->opr.op[1]);
case SIN: return sin(ex(p->opr.op[0]));
case COS: return cos(ex(p->opr.op[0]));
case B2D: return BtoD(p->opr.op[0]->str.value);
case FAC:
res = factorial(ex(p->opr.op[0]));
if(res==-1&&count==0){
printf("Fraction number is not allowed.\n");
count++;
return res;
}
else if(res==-2&&count==0){
printf("Negative number is not allowed.\n");
count++;
return res;
}
if(count==1) count = 0;
return res;
}
}
return 0;
}
float BtoD(char* bin)
{
int i = 0;
int dec = 0;
while(bin[i]!='B'){
if (bin[i] == '1') dec = dec * 2 + 1;
else if (bin[i] == '0') dec *= 2;
i++;
}
return dec;
}
float factorial(float fac){
if(fac<0){
return -2;
}
int fac2 = (int)fac;
if (fac2!=fac){
return -1;
}
float sum = 1;
while(fac!=0){
sum*=fac;
fac--;
}
return sum;
}
test.txt
x = 0;
while (x<3) {
print x;
x = x + 1;
}