概述
上一篇开场简略描绘了一下我对编译的看法,现在要开始动手做点东西。
在亲自完成词法和语法剖析器之前,先运用东西帮个忙。
运用Lex
和YACC
生成编译器,该编译器能解析核算四则运算。这儿完成的作用是输入一个四则运算,回车后输出成果,由于是一边输入一边解析履行,所以应该叫解析器更适宜。
对应代码放在github库上的calculator分支:
calculator
词法剖析
词法剖析读入输入字符串,按顺序解析其间包含的词,每次调用词法剖析器回来的内容叫token,token包类型(词的类型),以及射中的字符串详细是什么(又名特点)。后续的语法剖析通过token的类型组成语法结构,特点影响终究的语义。
关于每一种类型的token,都界说匹配的逻辑,匹配逻辑就用正则表达式描绘,词法剖析器一边读入字符,一边跟这些正则匹配,确认匹配某个正则,就确认了匹配的token的类型,回来的token包含了类型以及实践匹配的字符串。
Lex
用Lex
界说token匹配规矩,完好代码文件在这儿
%{
......
#include "nl.h"
#include "y.tab.h"
......
%}
%%
"+" return ADD;
"-" return SUB;
"*" return MUL;
"/" return DIV;
"%" return MOD;
"(" return LP;
")" return RP;
"\n" return CR;
([1-9][0-9]*)|"0" {
Expression *expression = nl_alloc_expression(INT_EXPRESSION);
sscanf(yytext, "%d", &expression->u.int_value);
yylval.expression = expression;
return INT_LITERAL;
}
[0-9]+\.[0-9]+ {
Expression *expression = nl_alloc_expression(DOUBLE_EXPRESSION);
sscanf(yytext, "%lf", &expression->u.double_value);
yylval.expression = expression;
return DOUBLE_LITERAL;
}
[ \t] ;
. {
printf("lexical error with unexpected charactor %s\n", yytext);
exit(1);
}
%%
顶部引进了类型文件nl.h
而词法剖析回来的token类型界说在y.tab.h
文件,该文件是YACC
生成语法剖析器时生成的,后边会介绍语法剖析。
射中某个标点符号,比方’+’号,就回来ADD这个常量,常量代表类型,类型的界说能够在Lex文件榜首部分,也能够在YACC文件那儿界说,现在采用的是后者。
匹配整数的代码加上注释说明如下(别的匹配浮点数的正则类似)
// 匹配整数的正则,要么是0,要么是非0开头后续跟0到9的数字
([1-9][0-9]*)|"0" {
// 在别的的C文件界说的表达式类型,以及创立表达式的办法,办法传进去的参数指明晰要创立的表达式是整数表达式
Expression *expression = nl_alloc_expression(INT_EXPRESSION);
// 匹配射中的字符串寄存在yytext这个外部变量中,这儿便是把匹配到的整数字符串转换成整数,再赋值给表达式的值
sscanf(yytext, "%d", &expression->u.int_value);
// 把创立的表达式对象存起来,YACC那儿运转时能够拿到
yylval.expression = expression;
// 回来token类型,YACC结构语法逻辑时要用
return INT_LITERAL;
}
还有匹配空白字符的逻辑,这儿便是匹配空格和tab之后什么都不做,直接忽略,至于换行符在前面加了匹配逻辑。
[ \t] ;
终究还有一个匹配逻辑是一个点号’.’,点号在正则里边表明任意字符,当前面的匹配都没有射中,终究射中任意字符都判定为过错,所以会打印一个过错提示,并终止程序。
总的来说,现在词法剖析会企图去匹配四则运算符,求模,整数,浮点数,小括号这些类型的token。
类型界说
在介绍语法剖析之前,先介绍类型和办法逻辑,由于语法剖析时一边剖析一边需求履行逻辑代码。
类型界说完好文件在这儿
这儿要完成的是无类型言语,而且在四则运算中,整数和浮点数是区别对待的,虽然也能够把一切数字都作为浮点数处理,但本文并不计划这样做,而是区分整数和浮点数两种类型。所以这儿界说值类型枚举(ValueType),同时界说值数据类型结构体(NL_Value),NL_Value能够寄存值的类型以及详细值是什么,详细值放在联合体u里边,假如值类型是整数,就放在int_value特点中,假如值类型是浮点数,就放在double_value中。
typedef enum {
INT_VALUE = 1,
DOUBLE_VAULE
} ValueType;
typedef struct {
ValueType type;
union {
int int_value;
double double_value;
} u;
} NL_Value;
界说表达式数据类型,表达式的类型放在枚举(ExpressionType)中,整数和浮点数也是一种表达式,别的加减乘除和求模是别的的表达式类型,ExpressionType中终究一个枚举值是一个占位符。表达式类型结构体Expression_tag记载了表达式类型(type)以及该表达式的详细值放在联合体,依据详细类型决议放在联合体的哪个特点中。由于解析四则运算比较简略,所以能够在解析过程中边解析边算出成果,所以表达式详细值只记载成果,整数值或浮点数值。
typedef enum {
INT_EXPRESSION = 1,
DOUBLE_EXPRESSION,
ADD_EXPRESSION,
SUB_EXPRESSION,
MUL_EXPRESSION,
DIV_EXPRESSION,
MOD_EXPRESSION,
EXPRESSION_TYPE_PLUS
} ExpressionType;
struct Expression_tag {
ExpressionType type;
union {
int int_value;
double double_value;
} u;
};
接着界说生成表达式的办法(放在create.c文件)以及核算表达式值的办法(放在eval.c文件),语法剖析过程中一边剖析一边生成相应的表达式,便是调用了咱们界说的办法。
/* create.c */
Expression *nl_alloc_expression(ExpressionType type);
Expression *nl_create_minus_expression(Expression *exp);
Expression *nl_create_binary_expression(ExpressionType type, Expression *left, Expression *right);
/* eval.c */
NL_Value nl_eval_binary_expression(ExpressionType operator, Expression *left, Expression *right);
NL_Value nl_eval_expression(Expression *exp);
void nl_print_value(NL_Value *v);
办法界说
看create.c
文件,完好文件在这儿
顶部引进头文件,这儿重点引进了上面界说类型的文件nl.h
。
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include "nl.h"
界说创立表达式的办法,其实便是为Expression
类型分配空间,分配空间用C库函数malloc
,用表达式类型指针指向分配的空间,设置类型,回来指针。
Expression *
nl_alloc_expression(ExpressionType type) {
Expression *exp;
exp = malloc(sizeof(Expression));
exp->type = type;
return exp;
}
界说一个办法把值转换成表达式,现在只需整数和浮点数两种值,直接声明一个表达式结构体,这儿不是声明指针,直接声明结构体的话会自动分配空间,然后依据值类型设置表达式类型,以及设置对应值,终究回来表达式结构体。
static Expression
convert_value_to_expression(NL_Value *v) {
Expression exp;
if (v->type == INT_VALUE) {
exp.type = INT_EXPRESSION;
exp.u.int_value = v->u.int_value;
} else if (v->type == DOUBLE_VAULE) {
exp.type = DOUBLE_EXPRESSION;
exp.u.double_value = v->u.double_value;
} else {
printf("[runtime error] convert value with unexpected type:%d\n", v->type);
exit(1);
}
return exp;
}
界说创立二元表达式的办法,传入类型表明是哪种二元操作,还要传入左值和右值,左右值都是表达式,由于也有整数和浮点数表达式。这儿调用了核算二元表达式值得办法(nl_eval_binary_expression
),该办法界说在eval.c
文件,在nl.h
有声明,回来核算成果是值类型,然后调用上面界说的convert_value_to_expression
办法把值转成表达式,放在left
指针,这儿纯粹是复用了left
指针,赋值给left
时,前面加了星号*left
,这是C言语用法,表明取指针指向的值,而这儿是赋值,由于convert_value_to_expression
回来的是结构体,而不是指针。创立完二元表达式结构体之后回来指针。
Expression *
nl_create_binary_expression(ExpressionType type, Expression *left, Expression *right) {
NL_Value v;
v = nl_eval_binary_expression(type, left, right);
*left = convert_value_to_expression(&v);
return left;
}
还要界说创立一元负操作表达式的办法,传进去的是表达式,其实便是简略判别是整数仍是浮点数,再直接把对应值取负后存回去,终究回来传进来的表达式指针。这儿是由于一切表达式都是边解析边核算的,所以一切表达式肯定都是某个数值成果。但之后遇到更复杂情况时,左右表达式就不是简略的数值,就不能直接核算。
Expression *
nl_create_minus_expression(Expression *exp) {
if (exp->type == INT_EXPRESSION) {
exp->u.int_value = -exp->u.int_value;
} else if (exp->type == DOUBLE_EXPRESSION) {
exp->u.double_value = -exp->u.double_value;
}
return exp;
}
create.c
就只界说了这几个办法。
看eval.c
文件,完好文件在这儿
先引进头文件,咱们界说的类型文件是要害。
#include "nl.h"
#include <stdio.h>
#include <stdlib.h>
#include <math.h>
核算某种值类型表达式的值,其实便是声明值类型结构体,赋值对应type,再赋值传进来的值,这儿是为了发生对应值的一个数据结构。
static NL_Value
eval_int_expression(int value) {
NL_Value v;
v.type = INT_VALUE;
v.u.int_value = value;
return v;
}
static NL_Value
eval_double_expression(double value) {
NL_Value v;
v.type = DOUBLE_VAULE;
v.u.double_value = value;
return v;
}
别的再界说一个办法,包装了上面两种办法,其实便是穿进去表达式,判别是整数仍是浮点数类型,从而调用上面两者之一处理
static NL_Value
eval_expression(Expression *exp) {
NL_Value v;
switch (exp->type) {
case INT_EXPRESSION: {
v = eval_int_expression(exp->u.int_value);
break;
}
case DOUBLE_EXPRESSION: {
v = eval_double_expression(exp->u.double_value);
break;
}
case ADD_EXPRESSION:
case SUB_EXPRESSION:
case MUL_EXPRESSION:
case DIV_EXPRESSION:
case MOD_EXPRESSION:
case EXPRESSION_TYPE_PLUS:
default: {
printf("[runtime error] eval expression with unexpected type:%d\n", exp->type);
exit(1);
}
}
return v;
}
核算二元操作值得办法,分红两个,整数和浮点数分隔,终究传进去的指针用于寄存成果,调用方给出这个指针,核算完就能够从这个指针拿到成果。判别操作符类型,决议详细做什么核算。这儿的操作类型只能是某种二元操作表达式,除此外其他类型都是反常,遇到反常类型会打印出过错信息。核算整数和浮点数的二元操作,大致相同,终究求模的办法有点差异。
static void
eval_binary_int(ExpressionType operator, int left, int right, NL_Value *result) {
result->type = INT_VALUE;
switch (operator) {
case ADD_EXPRESSION: {
result->u.int_value = left + right;
break;
}
case SUB_EXPRESSION: {
result->u.int_value = left - right;
break;
}
case MUL_EXPRESSION: {
result->u.int_value = left * right;
break;
}
case DIV_EXPRESSION: {
result->u.int_value = left / right;
break;
}
case MOD_EXPRESSION: {
result->u.int_value = left % right;
break;
}
case INT_EXPRESSION:
case DOUBLE_EXPRESSION:
case EXPRESSION_TYPE_PLUS:
default: {
printf("[runtime error] eval binary int with unexpected type:%d\n", operator);
exit(1);
}
}
}
static void
eval_binary_double(ExpressionType operator, double left, double right, NL_Value *result) {
result->type = DOUBLE_VAULE;
switch (operator) {
case ADD_EXPRESSION: {
result->u.double_value = left + right;
break;
}
case SUB_EXPRESSION: {
result->u.double_value = left - right;
break;
}
case MUL_EXPRESSION: {
result->u.double_value = left * right;
break;
}
case DIV_EXPRESSION: {
result->u.double_value = left / right;
break;
}
case MOD_EXPRESSION: {
result->u.double_value = fmod(left, right);
break;
}
case INT_EXPRESSION:
case DOUBLE_EXPRESSION:
case EXPRESSION_TYPE_PLUS:
default: {
printf("[runtime error] eval binary int with unexpected type:%d\n", operator);
exit(1);
}
}
}
再界说二元表达式核算办法,其实便是对上面两种二元办法做了封装,通过判别传进来的表达式类型挑选适宜办法。只需左表达式或右表达式有一个是浮点数表达式,就把另一个整数表达式也转换成浮点数,然后调用浮点数二元核算办法核算。
NL_Value
nl_eval_binary_expression(ExpressionType operator, Expression *left, Expression *right) {
NL_Value left_val;
NL_Value right_val;
NL_Value result;
left_val = eval_expression(left);
right_val = eval_expression(right);
if (left_val.type == INT_VALUE && right_val.type == INT_VALUE) {
eval_binary_int(operator, left_val.u.int_value, right_val.u.int_value, &result);
} else if (left_val.type == DOUBLE_VAULE && right_val.type == DOUBLE_VAULE) {
eval_binary_double(operator, left_val.u.double_value, right_val.u.double_value, &result);
} else if (left_val.type == INT_VALUE && right_val.type == DOUBLE_VAULE) {
left_val.u.double_value = left_val.u.int_value;
eval_binary_double(operator, left_val.u.double_value, right_val.u.double_value, &result);
} else if (left_val.type == DOUBLE_VAULE && right_val.type == INT_VALUE) {
right_val.u.double_value = right_val.u.int_value;
eval_binary_double(operator, left_val.u.double_value, right_val.u.double_value, &result);
} else {
printf("[runtime error] eval binary expression with unexpected type, left:%d, right:%d\n", left_val.type, right_val.type);
exit(1);
}
return result;
}
又界说一个对外运用的核算表达式值得办法,其实便是调用了上面界说好的办法。
NL_Value
nl_eval_expression(Expression *exp) {
return eval_expression(exp);
}
还界说了一个打印办法,用来打印某个值的成果。
void
nl_print_value(NL_Value *v) {
if (v->type == INT_VALUE) {
printf("--> %d\n", v->u.int_value);
} else if (v->type == DOUBLE_VAULE) {
printf("--> %lf\n", v->u.double_value);
}
}
就这些。
YACC
接着讲语法剖析。以YACC
能读懂的方法写下语法规矩,让YACC
读入规矩文件后生成语法剖析器。
完好的文件在这儿
YACC
文件分红三部分,先说榜首部分
引进必要的C文件,声明必要的办法,C言语里边,调用办法之前要先声明,声明必须在调用之前,界说能够写在调用后边。C言语没有像JavaScript那样的变量或办法提升的行为。界说类型的文件nl.h
仍是需求的,声明晰yylex
办法,该办法在Lex
生成的词法剖析器C文件里边界说,该办法便是用来回来token的。别的声明的yyerror
办法在语法剖析出错时调用,在YACC
文件第三部分界说。
%{
#include <stdio.h>
#include <stdlib.h>
#include "nl.h"
int yylex();
int yyerror(char const *str);
%}
界说类型,用于指派给一些token或非终结符,表明解析到实例时,该实例的值是什么类型。其实光这样说是很难让人明白的,直接看下面的实践例子吧。这儿界说了expression
类型,这只是一个类型姓名,而它的实践类型是Expression
指针,在nl.h
里边界说。
%union {
Expression *expression;
}
界说一些token,其实是token的类型,用常量的方法表明,比方这儿界说了常量ADD
,表明一种token类型,匹配什么样的字符时回来这个类型的token在词法剖析那儿界说,那儿界说了射中加号(‘+’)时回来ADD
类型的token。榜首行都是些没有值的token类型,第二行表明整数和浮点数两种类型的token,他们回来的值是expression
类型的。
%token ADD SUB MUL DIV LP RP MOD CR
%token <expression> INT_LITERAL DOUBLE_LITERAL
再界说非终结符,非终结符便是由其他非终结符和token组成的元素,当然,其它非终结符也是由非终结符和token组成,但一切非终结符往下推导,终究都是由token组成。界说非终结符用%type
开头,他们回来的值类型是expression
。可能不能了解非终结符是什么,往后看可能就明白了,其实便是一些语法安排中一类元素的代号。
一行双百分号表明榜首部分完毕。
%type <expression> primary_expression mult_expression add_expression expression
%%
看第二部分,界说各个发生式,也组成了语法结构
界说表达式列表(expression_list
),它能够是单个表达式(expression
),也能够是另一个表达式列表后边跟一个表达式。
expression_list
: expression
| expression_list expression
;
比方,有这样一个表达式
3 + 6;
它是一个表达式列表,它也是单个表达式
再比方,有两个表达式
4 – 19 – 4;
23 + 45 * 34;
把榜首个表达式看做一个表达式列表(expression_list
),第二个表达式看做单个表达式(expression
),这样expression_list
后边跟一个expression
,组成了一个新的expression_list
。
发生式都是递归概念的。
再界说表达式的发生式,表达式能够是一个加法表达式后边跟回车,也能够是不跟回车的加法表达式,但假如是遇到跟回车的加法表达式时,需求履行大括号里边的逻辑。
expression
: add_expression CR
{
NL_Value v = nl_eval_expression($1);
nl_print_value(&v);
}
| add_expression
;
大括号的逻辑里,用到nl_eval_expression
办法,该办法之前介绍过,界说在eval.c
文件,在nl.h
也声明晰。$1
表明发生式体中榜首个元素(add_expression
)回来的值,榜首部分界说%type
的时分指明晰add_expression
回来的类型是expression
,实践类型是Expression
指针,而Expression
在nl.h
里边有界说。
nl_eval_expression
核算表达式的值,回来NL_Value
类型的值,然后调用nl_print_value
办法打印出来。
界说add_expression
的发生式,它能够是单个乘法表达式(mult_expression
),也能够是另一个加法表达式(add_expression
)加上(ADD
)一个乘法表达式(mult_expression
),还能够是另一个加法表达式(add_expression
)减去(SUB
)一个乘法表达式(mult_expression
)
add_expression
: mult_expression
| add_expression ADD mult_expression
{
$$ = nl_create_binary_expression(ADD_EXPRESSION, $1, $3);
}
| add_expression SUB mult_expression
{
$$ = nl_create_binary_expression(SUB_EXPRESSION, $1, $3);
}
;
之所以这样设计,是为了让乘法有更高的核算优先级,当一个表达式包含加减法和乘除法时,整体看作是一个加法表达式,而里边的乘除法表达式肯定要先核算,构成乘法表达式(mult_expression
)才能满意加法表达式界说的结构。这儿的界说也表明晰单个乘法表达式也是一个加法表达式。
当遇到包含加号或减号的加法表达式时,就会调用nl_create_binary_expression
办法,创立一个加法表达式,$1
和$3
表明发生式体中榜首个元素(add_expression
)和第三个元素(mult_expression
),他们都是回来表达式类型的值。而$$
表明终究这一整个加法表达式的值,也是一个Expression
指针。
可能有人觉得不好了解,等介绍完悉数发生式之后举个例子。
界说乘法表达式,乘法表达式能够是一个根底表达式,或者是另一个乘法表达式乘以(或除以/或模)另一个乘法表达式
mult_expression
: primary_expression
| mult_expression MUL primary_expression
{
$$ = nl_create_binary_expression(MUL_EXPRESSION, $1, $3);
}
| mult_expression DIV primary_expression
{
$$ = nl_create_binary_expression(DIV_EXPRESSION, $1, $3);
}
| mult_expression MOD primary_expression
{
$$ = nl_create_binary_expression(MOD_EXPRESSION, $1, $3);
}
;
根底表达式,能够是另一个根底表达式取负值,能够是括号包裹另一个表达式,能够是一个整数或浮点数。
primary_expression
: SUB primary_expression
{
$$ = nl_create_minus_expression($2);
}
| LP expression RP
{
$$ = $2;
}
| INT_LITERAL
| DOUBLE_LITERAL
;
发生式就这些,下面举例测验说明
比方这个表达式
3
解析是自上而下的,3归于整数,会被识别为INT_LITERAL
,INT_LITERAL
归于primary_expression
,而依据发生式界说primary_expression
归于mult_expression
,同理mult_expression
归于add_expression
,一路归于到expression_list
。
比方这个表达式
3 + 2
3能够一路归于到add_expression
,同理,2归于mult_expression
,它们之间有个加号,就满意了add_expression ADD mult_expression
这个结构,触发界说的逻辑履行,创立加法表达式结构体变量并回来。
比方这个表达式
3 + 3 + 4
前面的3 + 3就像上一个例子,会终究回来一个加法表达式add_expression
,然后4归于mult_expression
,两者相加,又满意了add_expression ADD mult_expression
结构,又触发创立加法表达式。
比方这个表达式
5 + 4 * 8
5归于add_expression
,遇到4之后发现它后边是个乘号,加法表达式本身不能处理乘号,只能由乘法表达式处理,所以当遇到后边是一个乘号时,5 + 4不会独自组成add_expression
,而是会继续看乘号后边的内容,发现是一个8,而4能够归于mult_expression
,8归于primary_expression
,两者加上中间的乘号满意mult_expression MUL primary_expression
,会生成并回来mult_expression
,前面的5归于add_expression
,这样5加上后边的乘法表达式,又构成一个add_expression
,又触发add_expression
的核算。
比方这个表达式
4 * 5 * (1 + 6)
4 * 5会构成mult_expression
,由于5后边跟乘号不影响前面4 * 5先组合,然后遇到括号,依据界说,它会认为遇到了primary_expression
,并企图在遇到右括号之前构建一个expression
,然后它会像从头解析一样终究解析出了1 + 6是一个add_expression
,而add_expression
也归于expression
,加上左右括号构成primary_expression
,由于前面的4 * 5归于mult_expression
,所以又契合了mult_expression MUL primary_expression
结构,然后触发生成逻辑,接着能够一路往上归于。
yacc
文件还有终究一部分,这儿就界说了yyerror办法,榜首部分声明过了,这儿界说,在语法剖析报错时履行该办法
%%
int yyerror(char const *str) {
extern char *yytext;
fprintf(stderr, "parse error near %s\n", yytext);
return 0;
}
生成词法剖析器和语法剖析器代码文件
我用的是mac,装置yacc
和lex
这两个东西,也是两个新指令。
假如用windows,要装置别的两个东西,bison
和flex
,查一下怎样装置就行,指令跟yacc
和lex
是差不多的。
先用yacc做编译
yacc -dv nl.y
编译了nl.y
文件后,生成y.tab.h
和y.tab.c
文件,而写lex
文件nl.l
时,开头就引进了y.tab.h
文件。
接着用lex
编译nl.l
lex nl.l
它会生成lex.yy.c
文件
这些生成的C文件,里边就包含了词法和语法剖析的功能,也包含了语法剖析的办法。
编译履行
结合咱们界说的办法,以及词法和语法剖析办法,咱们需求写一个C的入口文件main.c
,引进必要头文件,main
办法是必须的。完好文件在这儿。
#include <stdio.h>
#include <stdlib.h>
int main(int argc, char **argv) {
extern int yyparse(void);
extern FILE *yyin;
yyin = stdin;
if (yyparse()) {
fprintf(stderr, "Error ! \n");
exit(1);
}
}
main
办法里边声明晰外部办法yyparse
和外部指定输入的变量yyin
,把yyin
赋值了stdin,表明读入的是规范输入的内容,根本便是键盘输入的内容。
接着的if判别里边调用yyparse
办法,该办法就会履行整个语法剖析,这包含了读入输入的字符,做词法剖析,做语法剖析,语法剖析过程中满意某些发生式逻辑时,履行咱们界说的逻辑。而咱们界说的逻辑包含了创立几种表达式,以及在遇到带回车的表达式时输出核算成果。
接着是调用gcc编译一切咱们写的文件以及yacc
和lex
生成的文件。我写了一个Makefile
文件(完好文件在这儿)只需一个make指令就能履行编译,生成一个可履行的nl
文件。
直接用指令履行它,他会等待你的输入,每当输入一个四则运算表达式,按回车,就会打印成果,能够继续输入下一个,比方输入下面的表达式
4 + 8 + 9 * 4 * (4+4*3+(3)+ -4)
它能输出正确成果,注意这个表达式终究是加上一个负四,现在是合法了。别的值得注意的是,做二元运算时,假如其间一个值是浮点数,则另一个整数也会先转成浮点数再核算,别的,浮点数在词法剖析时确定,有小数点的都是浮点数,现在的词法界说中,像这样(45.)小数点后没有数字的格局是过错的。
下面补一个本地操作的录屏,由于mac电脑不在身边,用的是windows上的ubuntu,而且用的不是yacc
和lex
,而是bison
和flex
,但其实便是换个指令而已,在Makefile上换一下就好,lex
直接换成flex
,而yacc
换成bison
,但由于bison
生成的文件不是固定文件名,而是依据输入文件名而定,所以这儿写死了输出文件名跟yacc
共同,编译的时分有个warning,应该是该C的规范版别不支持打印时用%lf,但问题不大。终究编译后会生成一些.o
文件,这是不必须的,yacc
生成y.tab.c
和y.tab.h
,而lex的文件中也有引进y.tab.h
。其实用Makefile
便是为了把几个编译步骤写在一同,先编译语法,再编译词法,再集合一切依赖的c文件编译出一个可履行文件。
完毕
这篇完毕,下一篇稍微修改一下输入方法,以及解说表达式做小调整,就完成了一门四则运算言语。
进入下一篇