概述

上一篇开场简略描绘了一下我对编译的看法,现在要开始动手做点东西。

在亲自完成词法和语法剖析器之前,先运用东西帮个忙。

运用LexYACC生成编译器,该编译器能解析核算四则运算。这儿完成的作用是输入一个四则运算,回车后输出成果,由于是一边输入一边解析履行,所以应该叫解析器更适宜。

对应代码放在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指针,而Expressionnl.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_LITERALINT_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,装置yacclex这两个东西,也是两个新指令。

假如用windows,要装置别的两个东西,bisonflex,查一下怎样装置就行,指令跟yacclex是差不多的。

先用yacc做编译

yacc -dv nl.y

编译了nl.y文件后,生成y.tab.hy.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编译一切咱们写的文件以及yacclex生成的文件。我写了一个Makefile文件(完好文件在这儿)只需一个make指令就能履行编译,生成一个可履行的nl文件。

直接用指令履行它,他会等待你的输入,每当输入一个四则运算表达式,按回车,就会打印成果,能够继续输入下一个,比方输入下面的表达式

4 + 8 + 9 * 4 * (4+4*3+(3)+ -4)

它能输出正确成果,注意这个表达式终究是加上一个负四,现在是合法了。别的值得注意的是,做二元运算时,假如其间一个值是浮点数,则另一个整数也会先转成浮点数再核算,别的,浮点数在词法剖析时确定,有小数点的都是浮点数,现在的词法界说中,像这样(45.)小数点后没有数字的格局是过错的。

下面补一个本地操作的录屏,由于mac电脑不在身边,用的是windows上的ubuntu,而且用的不是yacclex,而是bisonflex,但其实便是换个指令而已,在Makefile上换一下就好,lex直接换成flex,而yacc换成bison,但由于bison生成的文件不是固定文件名,而是依据输入文件名而定,所以这儿写死了输出文件名跟yacc共同,编译的时分有个warning,应该是该C的规范版别不支持打印时用%lf,但问题不大。终究编译后会生成一些.o文件,这是不必须的,yacc生成y.tab.cy.tab.h,而lex的文件中也有引进y.tab.h。其实用Makefile便是为了把几个编译步骤写在一同,先编译语法,再编译词法,再集合一切依赖的c文件编译出一个可履行文件。

完毕

这篇完毕,下一篇稍微修改一下输入方法,以及解说表达式做小调整,就完成了一门四则运算言语。

进入下一篇