学习制作一门有趣的编程语言-0x02

本节代码CalcWithTable

上次我们使用Lex & Yacc制作了一个能够处理优先级的计算器,其中的优先级的设定是通过修改文法,将优先级提升,这次重写这个计算器并添加一些新的功能。

先看Lex文件


%{

#include "y.tab.h"
#include <math.h>
#include "link_list.h"

%}

%%
    /* 这段正则和之前都有所不同 明显的增加了对于科学技术法的支持 */
([0-9]+|([0-9]*\.[0-9]+)([eE][-+]?[0-9]+)?) {
          /* 转换为double类型 */
          yylval.dval = atof(yytext);
          /* 我把它打印出来 */
          printf("number : %s \n", yytext);
          return NUMBER;
    }

[ \t] ;

    /* 这里提供了对于字母和字母组合的识别 这是对于变量名的识别匹配 */
[A-Za-z][A-Za-z0-9]* {
          /* addNode(char *)是link_list.c中的函数 将变量名和对应的参数存储在一个链表中 */
          /* 返回值为对应的指针 */
          yylval.name = addNode(yytext);
          printLink();
          /* 返回的Token指变量名 */
          return NAME;
      }

"$"   {
          return 0;
      }

\n    |

.     {
            return yytext[0];
      }

%%

这段代码和之前的区别不大,但是我们明显的为它增加了一个功能,就是识别变量名。

在看yacc:


%{

#include "link_list.h"
int yydebug=1;
#include <math.h>

%}

%union{
    double dval;
    /* 存储的类型变了 存储的是一个symtable类型的结构体指针 */
    struct symtable *name;
}

    /* 声明了两个Token 指针类型的NAME double类型的NUMBER */
%token <name> NAME
%token <dval> NUMBER

    /* 这里强行指定了左结合以及优先性 */
%left '-' '+'
%left '*' '/'
    /* 为一元减号设置了最高的优先级 */
%nonassoc UMINUS

%type <dval> expression

%%

stament_list: stament '\n'
            | stament_list stament '\n'
            ;

        /* 增加了一个规则处理变量名赋值 */
stament: NAME '=' expression { $1->value = $3; }
       | expression { printf("= %g \n", $1); }
       ;

expression: expression '+' expression { $$ = $1 + $3; }
          | expression '-' expression { $$ = $1 - $3; }
          | expression '*' expression { $$ = $1 * $3; }
          | expression '/' expression {
              if($3 == 0.0){
                  yyerror("devide by zero");
              }else{
                  $$ = $1 / $3;
              }
          }
                  /* 这里再只有一元建好操作符的时候强制使用UMINUS的结合性 */
          | '-' expression %prec UMINUS { $$ = -$2; }
          | '(' expression ')' { $$ = $2; }
          | NUMBER
                  /* 当出现变量名的时候 把变量名对应的值返回 */
          | NAME   { $$ = $1->value; }
                  /* 这也是新功能 计算器支持使用内置函数 当变量名存在函数指针的时候 */
                  /* 将其作为函数处理 */
          | NAME '(' expression ')' {
              if ($1->funcptr)
              {
                $$ = ($1->funcptr)($3);
              }else{
                printf("%s is not a function \n", $1->name);
                $$ = 0.0;
              }
          }
          ;

%%

int main(){

    extern double sqrt(), exp(), log();

    /* 现在无法动下的生成函数 所以只能添加内置的几种函数 */
    addFunction("sqrt", sqrt);
    addFunction("exp",exp);
    addFunction("log",log);

    yyparse();
}

现在的yacc文件中我们支持了变量名的申请,和函数的调用,这里面我们的函数都来自于 link_list.h 中的定义

link_list.h :


#ifndef SYMBOL
#include <string.h>
#include <stdio.h>
#include <stdlib.h>
struct symtable {
  /* data */
    char *name;
    double value;
    /* 函数指针 */
    double (*funcptr)();
    struct symtable *next;
};

struct symtable* addNode(char *symbol);
void printLink();
void addFunction(char *name, double func());
#endif

link_list.c :


#include "link_list.h"

struct symtable *head = 0;

int flag = 0;

struct symtable* addNode(char *symbol){

    if (flag == 0) {
        flag = 1;
        head = (struct symtable*)malloc(sizeof(struct symtable));
        head->name = strdup(symbol);
        head->next = 0;
        return head;
    }

    struct symtable *sp = head;

    while (sp != 0) {
        if (sp->name && !strcmp(sp->name, symbol)) {
          /*  找到一样的了 */
            return sp;
        }

        if (!sp->name) {
            sp->name = strdup(symbol);
            return sp;
        }

        sp = sp->next;
    }

    sp = head;

    while (sp->next != 0) {
        sp = sp->next;
    }

    struct symtable *tail;
    tail = (struct symtable*)malloc(sizeof(struct symtable));
    tail->name = strdup(symbol);
    tail->next = 0;
    sp->next = tail;
    return tail;
}

void printLink(){
  struct symtable* sp = head;
  while (sp != 0) {
      printf("name : %s index : %p value : %f \n", sp->name, sp, sp->value);
      sp = sp->next;
  }
}

void addFunction(char *name, double func()){
    struct symtable* sp = addNode(name);
    sp->funcptr = func;
}

至此我们的计算器就能支持参数名和内置函数了。

我们来写一下makefile文件:



ex_calc: y.tab.o lex.yy.o link_list.o
    $ cc -o ex_calc link_list.c y.tab.c lex.yy.c -ly -ll

link_list.o: link_list.c link_list.h

lex.yy.o: lex.yy.c y.tab.h

lex.yy.o y.tab.o: link_list.h

y.tab.c y.tab.h: ex_calc.y
            $ yacc -d ex_calc.y

lex.yy.c: ex_calc.l
    $ lex ex_calc.l

之后使用make命令编译运行,就可以正常使用了。

我来评几句
登录后评论

已发表评论数()

相关站点

+订阅
热门文章