Compiler Construction using Flex and Bison(译7--代码生成)

本文介绍了一种针对Simple语言的编译器实现方法,详细阐述了如何通过语法分析树生成中间代码,并最终转化为虚拟机的目标代码。文章重点讨论了代码生成器的工作原理,包括数据段、代码段和表达式栈的设计,以及如何处理诸如if和while等结构化语句。

第七章

代码生成
当源程序被加工,它被转成一种中间形式.我们例子的中间表示是一个隐式的语法分析树.其它类似汇编代码的中间表示也可以采用.代码生成器把中间形式转换成目标代码.通常,目标代码是一个虚拟机的程序.Simple的虚拟机包含三个段.一个数据段,一个代码段和一个表达式栈.
数据段包含与变量相关联的值.每个变量被分配一个用于保存相关值的单元.这样,代码生成的一部分就要是动态的把每个变量与一个地址相关联.代码段包含一系列运算.程序中的常数也包含在代码段中,因为它们的值是不会更改的.表达式栈是一个用于保存计算表达式时的中间值的一个栈.表达式栈的存在也指出了Simple虚拟机是一个"堆栈机"

声明翻译:
声明定义了一个环境.为了预留空间给数据值,DATA这条指令会用到.如:

   integer x,y,z     DATA 2

语句翻译
赋值,if,while,read 和write语句按如下翻译:

x := expr      expr的代码
STORE X

if cond then     cond的代码
          S1      BR_FALSE L1
else       S1的代码
  S2      BR L2
end      L1:  S2代码
L2:

while cond do    L1: cond的代码
S        BR_FALSE L2
end       S的代码
          BR L1
L2:

read X     IN_INT X

write expr    expr的代码
OUT_INT

如果代码是放在一个数组中,那么标号地址必须等到有效时才能回填到代码中

表达式翻译
表达式在一个表达式栈中求值.表达式按如下翻译:

constant      LD_INT constant

variable      LD variable

e1 op e2      e1的代码
e2的代码
op的代码

代码生成模块

数据段偏移由0开始且在数据段中可以空间预留,通过调用data_location函数则返回保留空间的地址.

int data_offset = 0;
int data_location() { return data_offset++; }

代码段偏移由0开始.在代码段中空间被预留,通过调用reserve_loc函数返回保留单元的地址.函数gen_lable返回代码偏移的值.

int code_offset = 0;
int reserve_loc()
{
return code_offset++;
}
int gen_lable()
{
return code_offset;
}

函数reserve_loc和gen_label是用于回填代码的.

函数gen_code和back_patch被用于生成代码.gen_code生成代码在当前偏移而back_patch被用于在先前预留的地址中生成代码

void gen_code( enum code_ops operation, int arg )
{ code[code_offset].op  = operation;
 code[code_offset++].arg = arg;
}
void back_patch( int addr, enum code_ops operation, int arg )
{
     code[addr].op = operation;
 code[addr].arg = arg;
}

符号表修改

符号表记录被扩展成包含相对数据段基地址的偏移(存储区应包含与每个变量相关联的值),而函数putsym函数扩展成把偏移存入与其相关的变量的记录中.

struct symrec
{
char *name;   /*name of symbol  */
int  offset;   /*data offset   */
struct symrec *next; /*link field   */
};
...
symrec * putsym (char *sym_name)
{
symrec *ptr;
ptr = (symrec *)malloc (sizeof(symrec));
ptr ->name = (char *)malloc (strlen(sym_name+1));
strcpy (ptr->name,sym_name);
ptr->offset = data_location();
ptr->next = (struct symrec *)sym_table;
sym_table = ptr;
return ptr;
}

语法分析器的修改
作为代码生成的例子,我们为Simple能生成一个栈机的代码而扩展我们的Lex和Yacc文件.首先,我们必须扩展Yacc和Lex文件把一个常数值由扫瞄器传到语法分析器中.在Yacc文件中定义的语义记录应修改,即把要返回的常作为语义记录的一部分.并且要保存两个标记的定义,这是if和while语句翻译时需要的.IF和WHILE的记号类型是能够支持为回填而进行的标记存储的<lbls>.函数newlblrec产生为保存因if和while语句生成代码需要的标记空间.context_check函数被扩展成生成代码.

%{#include <stdio.h>    /*For I/O         */
#include <stdlib.h>     /*For malloc here  and in symbol table  */
#include <string.h>     /*For strcmp in symbol table     */
#inlcude "ST.h"     /*Symbol Table       */
#include "SM.h"     /*Stack Machine       */
#include "CG.h"     /*Code Generator       */
#define YYDEBUG 1   /*For Debugging       */
int  errors;      /*Error Count-incremented in CG,ckd here  */
 struct  lbs      /*For lables : if  and while     */
{
int for_goto;
int for_jmp_false;
};
struct  lbs *newlblrec()    /*Allocate space for the labels    */
{
return (struct lbs *) malloc(sizeof(struct lbs));
}
install ( char *sym_name )
{
symrec *s;
s = getsym (sym_name);
if (s == 0)
s = putsym (sym_name);
else { errors++;
     printf( "%s is already defined/n", sym_name );
}
}
context_check( enum code_ops operation, char *sym_name )
{ symrec *identifier;
  identifier = getsym( sym_name );
      if ( identifier == 0 )
 { errors++;
           printf( "%s", sym_name );
           printf( "%s/n", " is an undeclared identifier"  );
 }
else gen_code( operation, identifier->offset );
}
%}
%union semrec     /* The Semantic Records     */
{
int  intval;     /* Integer valus          */
char  *id;     /* Identifiers        */
struct  lbs *lbls    /* For backpatching       */
}
%start program
%token  <intval> NUMBER   /* Simple  integer       */
%token <id>    IDENTIFIER  /* Simple  identifier      */
%token <lbls>  IF  WHILE  /* For  backpathing lables     */
%token SKIP THEN ELSE FI DO END
%token INTEGER READ WRITE LET IN
%token ASSGNOP
%left '-' '+'
%left '*' '/'
%right '^'
%%
/* Grammar Rules and Actions */
%%
/* c subroutines */

语法分析器被扩展成生成和组合代码.代码执行if和while命令必须包含有正确的转移地址.在我们的例子中,转移的目的地是标记.因为目的地是不知道的,直到全部命令被处理,所以要求采用回填技术去回填转移地址的信息.在例子中,当语法分析器知道要需要生成一个转移地址时,标记标识符将被生成.当知道标记的真实地址时,这个标记才写入生成代码中.一个可选的方法是把代码存储在一个数组中,并回填真实的地址.

与基于一个堆栈机体系的代码生成相关联的操作被添加到语法段中.为声明段的代码生成必须为变量预留空间.

/*C and Parser declarations */
%%
program : LET
declarations
IN   { gen_code( DATA, sym_table->offset );     }
commands
END  { gen_code( HALT, 0); YYACCEPT;     }
;
declarations : /* empty */
| INTEGER id_seq IDENTIFIER '.' { install( $3 );     }
;
id_seq : /*empty */
| id_seq IDENTIFIER ','   { install( $2) ;      }

IF和WHILE操作要求回填.

command : /*empty */
| commands command ';'
;
command  : SKIP
| READ IDENTIFIER { context_check( READ_INT,$2);    }
| WRITE exp   { gen_code( WRITE_INT, 0 );    }
| IDENTIFIER ASSGNOP exp { context_check( STORE, $1 );   }

| IF exp    {$1 = (struct lbs *)new1blrec();
                                     $1->for_jmp_false = reserve_loc();   }
 THEN commands  { $1->for_goto = reserve_loc();    }
 ELSE    { back_patch( $1->for_jmp_false,
JMP_FALSE,
gen_label() );     }
   commands
 FI     { back_patch( $1->for_goto,GOTO,gen_label() ); }

| WHILE    { $1 = (struct lbs *)newlblrec();
 $1->for_goto = gen_lable();     }
  exp    {$1->for_jump_false = reserve_loc();   }
 DO
commands
 END    { gen_code( GOTO,$1->for_goto );
            back_patch( $1->for_jmp_false,
 JMP_FALSE,
gen_label() );    }
;
表达式的代码生成是直接翻译的.
exp : NUMBER    { gen_code( LD_INT,$1 );    }
   | IDENTIFER    { context_check( LD_VAR, $1 );   }
   | exp '<' exp    { gen_code( LT,0 );      }
   | exp '=' exp    { gen_code( EQ,0 );     }
   | exp '>' exp    { gen_code( GT,0 );     }
   | exp '+' exp    { gen_code( ADD,0 );     }
   | exp '-' exp    { gen_code( SUB,0 );     }
               | exp '*' exp    { gen_code( MULT,0 );     }
   | exp '/' exp    { gen_code( DIV,0  );     }
   | exp '^' exp    { gen_code( PSW, 0 );     }
               | '('  exp  ')'
;
%%
/* C subroutines */

 

 


扫瞄器修改

Lex文件被扩展成把常数值放入到语义记录中.

%{
#include <string.h>     /* for strdup      */
#include "simple.tab.h"     /* for token definitions and yylval  */
%}
DIGIT  [0-9]
ID   [a-z][a-z0-9]*
%%
{DIGIT}+ {yylval.intval = atoi( yytext );
  return (INT);  }
...
{ID}  {yylval.id = (char *) strdup(yytext);
  return (IDENT);   }
[ /t/n]+      /*eat up whitespace */
.   {return(yytext[0]); }
%%

一个例子

为了说明这个编译器代码生成的能力,图7.1是一个Simple程序而图7.2是其生成代码.

let
integer n,x;
in
read n;
if n < 10 then x :=1; else skip ; fi;
while n < 10 do x := 5*x; n :=n+1; end;
skip;
write n;
write x;
end
Figure 7.1 : A Simple program

0:   data     1
1:   in_int    0
2: ld_var    0
3: ld_int      10
4: lt     0
5: jmp_false   9
6: ld_int    1
7: store    1
8: goto     9
9: ld_var    0
10: ld_int    10
11:  lt     0
12: jmp_false   22
13: ld_int    5
14: ld_var    1
15: mult    0
16: store    1
17: ld_var    0
18: ld_int    1
19: add     0
20: store    0
21: goto     9
22: ld_var    0
23: out_int    0
24: ld_var    1
25: out_int    0
26: halt     0
                            Figure 7.2 : Stack code

内容概要:本文围绕新一代传感器产品在汽车电子电气架构中的关键作用展开分析,重点探讨了智能汽车向高阶智能化演进背景下,传统传感器无法满足感知需求的问题。文章系统阐述了自动驾驶、智能座舱、电动化与网联化三大趋势对传感器技术提出的更高要求,并深入剖析了激光雷达、4D毫米波雷达和3D-ToF摄像头三类核心新型传感器的技术原理、性能优势与现存短板。激光雷达凭借高精度三维点云成为高阶智驾的“眼睛”,4D毫米波雷达通过增加高度维度提升环境感知能力,3D-ToF摄像头则在智能座舱中实现人体姿态识别与交互功能。文章还指出传感器正从单一数据采集向智能决策升级,强调车规级可靠性、多模态融合与成本控制是未来发展方向。; 适合人群:从事汽车电子、智能驾驶、传感器研发等相关领域的工程师和技术管理人员,具备一定专业背景的研发人员;; 使用场景及目标:①理解新一代传感器在智能汽车系统中的定位与技术差异;②掌握激光雷达、4D毫米波雷达、3D-ToF摄像头的核心参数、应用场景及选型依据;③为智能驾驶感知层设计、多传感器融合方案提供理论支持与技术参考; 阅读建议:建议结合实际项目需求对比各类传感器性能指标,关注其在复杂工况下的鲁棒性表现,并重视传感器与整车系统的集成适配问题,同时跟踪芯片化、固态化等技术演进趋势。
器构建是一个复杂的过程,需要使用一些工具和技术来实现。而FlexBison是非常流行的用于构建编器的工具之一。 Flex是一个用于生成词法分析器的工具,它基于一组正则表达式规则,将输入的字符流分解成一个个的词法单元。通过定义一些规则,Flex可以将输入的源代码转化成一系列的词法单元,供后续的语法分析使用。 而Bison是一个用于生成语法分析器的工具,它通过定义一组语法规则和语义动作,将词法分析生成的词法单元序列转化成一颗语法树。Bison可以根据语法规则,自动生成语法分析器的代码,通过递归下降的方式对输入的源代码进行语法分析。 使用FlexBison构建编器的过程通常分为以下几个步骤: 1. 设计词法规则:根据编程语言的词法规范,使用正则表达式定义一组词法规则,用于将输入的字符流拆分成一个个的词法单元。 2. 设计语法规则:根据编程语言的语法规范,使用BNF(巴科斯-诺尔范式)或类似的语法表示方法,定义一组语法规则,用于将词法单元转化成语法树。 3. 实现词法分析器:使用Flex根据词法规则生成词法分析器的C代码,该代码将输入字符流转化成一系列词法单元。 4. 实现语法分析器:使用Bison根据语法规则生成语法分析器的C代码,该代码将词法单元序列转化成语法树。 5. 实现语义动作:在Bison生成的语法分析器中,添加适当的语义动作,用于对语法树进行语义分析和中间代码生成等操作。 6. 实现语言特性:根据编程语言的特性,添加相应的功能,例如处理变量声明、函数调用、控制结构等。 通过以上步骤,使用FlexBison可以快速构建一个简单的编器。当然,这只是编器构建过程的一部分,还需要考虑符号表管理、优化和代码生成等方面。但FlexBison提供了强大的词法和语法分析功能,为编器的开发提供了很好的基础。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值