向 yacc 计算器添加函数

Adding functions to a yacc calculator

我正在 lex/yacc 中开发一个简单的计算器,并且我正在尝试将某些函数 return 作为双精度而不是整数。

我最初拥有 expr 下的所有内容。发现我无法键入 cast $$ 我引入了一种新类型 DECIMAL 并将其添加为新语法。

现在任何函数调用都会产生语法错误并终止程序。

我的代码:

%{
#define PI 3.14159265358979
#include <stdio.h>
#include <math.h>
int regs[26];
int base;
int yylex();
int yyerror(char *s);
int yywrap();
%}

%start list
%union {
  int a;
  double b;
  char c;
}
%type <a> expr number DIGIT
%type <c> LETTER
%type <b> DECIMAL
%token DIGIT LETTER
%token EXIT
%token SIN COS TAN SQRT LOG LN
%left '|'
%left '&'
%left '+' '-'
%left '*' '/' '%'
%left UMINUS
%right '^'

%nonassoc SIN COS TAN SQRT LOG LN


%%

list: /* empty */
    | list stat '\n'
    | list error '\n' {
      yyerrok;
    };
    | list EXIT {
      exit(EXIT_SUCCESS);
    }
    | DECIMAL ;

stat: expr {
        printf("%d\n", );
      }
    | LETTER '=' expr {
      regs[] = ;
    };

expr: '(' expr ')' {
        $$ = ;
      }
    | expr '*' expr {
        $$ =  * ;
      }
    | expr '/' expr {
        $$ =  / ;
      }
    | expr '%' expr {
        $$ =  % ;
      }
    | expr '+' expr {
        $$ =  + ;
      }
    | expr '-' expr {
        $$ =  - ;
      }
    | expr '&' expr {
        $$ =  & ;
      }
    | expr '|' expr {
        $$ =  | ;
      }
    | '-' expr %prec UMINUS {
        $$ = -;
      }
    
    | expr '^' expr{
      $$ = pow(,);
    }

    | LETTER {
        $$ = regs[];
      }

    | number;

DECIMAL:
    SIN DECIMAL {
      $$ = sin( * PI / 180);
    }

    | COS DECIMAL {
      $$ = cos( * PI / 180);
    }

    | TAN DECIMAL {
      $$ = tan( * PI / 180);
    }

    | SQRT DECIMAL {
      $$ = sqrt();
    }

    | LOG DECIMAL{
      $$ = log10();
    }

    | LN DECIMAL{
      $$ = log();
    }



number: DIGIT {
          $$ = ;
          base = ( == 0) ? 8 : 10;
        }
      | number DIGIT {
          $$ = base *  + ;
        };

%%

int main() {
  return yyparse();
}

int yyerror(char *s) {
  fprintf(stderr, "%s\n", s);
  return 1;
}

int yywrap() {
  return 1;
}

如果有帮助,这是我的 lex 代码

%{
#include <stdio.h>
#include "y.tab.h"

int c;
extern YYSTYPE yylval;
%}

%%

" ";


[a-z] {
  c = yytext[0];
  yylval.a = c - 'a';
  return(LETTER);
}

[0-9] {
  c = yytext[0];
  yylval.a = c - '0';
  return(DIGIT);
}

[^a-z0-9\b] {
  c = yytext[0];
  return(c);
}

sin {
  return SIN;
}

cos {
  return COS;
}

tan {
  return TAN;
}

sqrt {
  return SQRT;
}

log {
  return LOG;
}

ln {
  return LN;
}

exit {
  return EXIT;
}

为什么非终结符 DECIMAL 全部大写?是不是分不清是终端还是非终端?

无论如何,没有推导可以产生包含 DECIMAL 的句子形式,因为唯一可以产生 DECIMAL 的非终结符是 DECIMAL 本身,而且 every DECIMAL 的生成包含 DECIMAL 的另一个实例,因此它无法生成仅包含标记的序列。

换句话说,DECIMAL 既无法到达又没有生产力,这使它加倍无用。这使得各种功能的令牌也无法访问。 Bison 会警告您这一点。如果您不顾警告尝试使用已编译的解析器,您自然会发现对其中一个函数的任何使用都是语法错误;没有有用的语法包含函数。

每个解析器符号都必须有单一的值类型,而且变量的数据类型不是句法的;也就是说,解析器无法知道 x 是否包含整数或浮点值。 (除非你将变量名称划分为字母范围,例如,x 始终是双精度数,而 n 始终是整数,但这种想法几十年前就过时了。)

所以你有一些选择。

  1. 您可以将所有值设为 double,就像 JavaScript 那样。这是可行的,因为每个 32 位 int 都可以精确地表示一个 double;实际上,double 可以精确表示其大小适合 52 位的任何整数。 (但按位运算需要一些工作,除法变得模糊,这就是为什么 Python 有两个不同的除法运算符。)

  2. 你可以把你所有的价值观都做成“歧视结合”;也就是说,struct 包含指示数据类型的 enum 和包含数据值的 union

  3. 您可以构建 AST 而不是直接计算输入。构建 AST 后,您可以通过深度优先遍历找出每个节点的数据类型,并在必要时引入类型转换。然后您可以使用另一个深度优先遍历来评估表达式(甚至编译它)。但是您仍然需要类似于可区分联合的东西才能正确键入变量。

试图在语法中编码类型信息是一条死胡同。您可以尝试破解词法分析器,以便它为每个变量查询一个符号 table,但是您会发现很难编写无冲突语法。

在上述解决方案中,#1 对于计算器来说是最简单和最有效的,除非您希望能够使用 64 位整数。 #2 在技术上更胜一筹(恕我直言),因为它很容易扩展到字符串等其他数据类型,但工作量更大。 (这是一次很好的学习经历,但现在可能不是您的学习重点。)

如果您最终的计划是构建一个编译器,而不仅仅是一个计算器,那么#3 是可行的方法。如果您打算添加条件、循环或用户定义的函数,这也是您需要的。它可能会让您最清楚地了解评估的真正含义。但这肯定是更多的工作。