向 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
始终是整数,但这种想法几十年前就过时了。)
所以你有一些选择。
您可以将所有值设为 double
,就像 JavaScript 那样。这是可行的,因为每个 32 位 int
都可以精确地表示一个 double
;实际上,double
可以精确表示其大小适合 52 位的任何整数。 (但按位运算需要一些工作,除法变得模糊,这就是为什么 Python 有两个不同的除法运算符。)
你可以把你所有的价值观都做成“歧视结合”;也就是说,struct
包含指示数据类型的 enum
和包含数据值的 union
。
您可以构建 AST 而不是直接计算输入。构建 AST 后,您可以通过深度优先遍历找出每个节点的数据类型,并在必要时引入类型转换。然后您可以使用另一个深度优先遍历来评估表达式(甚至编译它)。但是您仍然需要类似于可区分联合的东西才能正确键入变量。
试图在语法中编码类型信息是一条死胡同。您可以尝试破解词法分析器,以便它为每个变量查询一个符号 table,但是您会发现很难编写无冲突语法。
在上述解决方案中,#1 对于计算器来说是最简单和最有效的,除非您希望能够使用 64 位整数。 #2 在技术上更胜一筹(恕我直言),因为它很容易扩展到字符串等其他数据类型,但工作量更大。 (这是一次很好的学习经历,但现在可能不是您的学习重点。)
如果您最终的计划是构建一个编译器,而不仅仅是一个计算器,那么#3 是可行的方法。如果您打算添加条件、循环或用户定义的函数,这也是您需要的。它可能会让您最清楚地了解评估的真正含义。但这肯定是更多的工作。
我正在 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
始终是整数,但这种想法几十年前就过时了。)
所以你有一些选择。
您可以将所有值设为
double
,就像 JavaScript 那样。这是可行的,因为每个 32 位int
都可以精确地表示一个double
;实际上,double
可以精确表示其大小适合 52 位的任何整数。 (但按位运算需要一些工作,除法变得模糊,这就是为什么 Python 有两个不同的除法运算符。)你可以把你所有的价值观都做成“歧视结合”;也就是说,
struct
包含指示数据类型的enum
和包含数据值的union
。您可以构建 AST 而不是直接计算输入。构建 AST 后,您可以通过深度优先遍历找出每个节点的数据类型,并在必要时引入类型转换。然后您可以使用另一个深度优先遍历来评估表达式(甚至编译它)。但是您仍然需要类似于可区分联合的东西才能正确键入变量。
试图在语法中编码类型信息是一条死胡同。您可以尝试破解词法分析器,以便它为每个变量查询一个符号 table,但是您会发现很难编写无冲突语法。
在上述解决方案中,#1 对于计算器来说是最简单和最有效的,除非您希望能够使用 64 位整数。 #2 在技术上更胜一筹(恕我直言),因为它很容易扩展到字符串等其他数据类型,但工作量更大。 (这是一次很好的学习经历,但现在可能不是您的学习重点。)
如果您最终的计划是构建一个编译器,而不仅仅是一个计算器,那么#3 是可行的方法。如果您打算添加条件、循环或用户定义的函数,这也是您需要的。它可能会让您最清楚地了解评估的真正含义。但这肯定是更多的工作。