C-编译器的实现

  写这个编译器的目的,是为了完成编译原理课上老师布置的大作业,实际上该大作业并不是真的实现一个编译器,而我选择硬刚,是为了完成我的小愿望--手写内核,编译器和CPU。我花了整个上半学期,写完了WeiOS,为了让它支持更多的用户态程序,甚至是基本的程序开发,必须给它量身打造一个编译器。于是这个编译器被提上日程。

  因为我要复习考研和专业课过多,我打消了手写词法分析和语法分析的念头,转而使用FLEXYACC,等到有时间再完成手工的版本--我认为也不是很难,如果用递归下降的话。

  全部代码都在该处 https://github.com/mynamevhinf/CMinusCompiler

词法分析

  因为是精简的C语言,所以只支持基本的符号(Token),像”++”, “--”和位操作都不予考虑。另外,只支持intvoid类型。于是便构成了以下符号集:

  number   [1-9][0-9]*

  letter     [a-zA-Z]

  Identifier  {letter}({letter}|{number})*

  Newline   \n

  whitespace [ \t\r]+

保留字:

  If else while int void return

运算和界限符号:

  <= >= != == + - * / < > ; , ( ) { } [ ]

主体函数是getToken(),这个函数封装了FLEX原生的yylex函数。而yyparser也将直接调用该函数。它的主要工作是在开始第一次词的检索前,初始化相关变量。然后在每次被调用的时候,返回符号的类型给yyparser,并且把构成符号的字符串临时保存在tokenString字符数组中。所以这函数相当于什么事情都没有干。

另外注意的是注释的两个符号,我直接在词法分析处理注释了。行注释是”//”,利用FLEX自带的input()函数(如果有的话,没有就写一个)一直读到’\n’出现。然后就是段注释符”/*”和”*/”,相似的做法。

语法分析

  以下是BNF格式的语法规则:

  Program -> declaration_list

  declaration_list -> declaration_list  declaration | declaration

  declaration ->  var_declaration | func_declaration

  type_specifier ->  INT | VOID

  var_declaration ->  type_specifier VARIABLE ;  |  type_specifier VARIABLE [ NUM ] ;

  func_declaration ->  type_specifier VARIABLE ( params ) compound_stmt

  Params -> params_list | VOID

  params_list -> params_list , param | param

  Param -> type_specifier VARIABLE  | type_specifier VARIABLE [ ]

  compound_stmt -> { local_declarations stmt_list }

  local_declarations -> local_declarations var_declaration | /* empty */

  stmt_list ->stmt_list stmt  |   stmt

  Stmt -> expr_stmt | if_stmt | return_stmt | compound_stmt | iteration_stmt

  expr_stmt -> expr ; | ;

  if_stmt ->IF ( expr ) stmt | IF ( expr )  stmt ELSE stmt

  iteration_stmt -> WHILE ( expr ) stmt

  return_stmt ->RET ; | RET expr ;

  Expr -> var = expr | simple_expr

  Var -> VARIABLE | VARIABLE [ NUM ]

  Call -> VARIABLE ( args )

  Args -> arg_list |  /* empty */

  arg_list -> arg_list , expr | expr

  simple_expr -> NUM  | var | call  | ( expr )  | - simple_expr

            | simple_expr + simple_expr

            | simple_expr - simple_expr

            | simple_expr * simple_expr

            | simple_expr / simple_expr

            | simple_expr < simple_expr

            | simple_expr > simple_expr

            | simple_expr >=simple_expr

            | simple_expr <= simple_expr

            | simple_expr != simple_expr

            | simple_expr == simple_expr

  我用globl.h中的TreeNode结构来保存语法树中的每一个节点。而一些为空的转换,我打算还是用一个该结构来表示,但是类型标记为None(也许有点浪费内存).

  我实现的C-还算是个比较完整的程序语言,所以很有必要生成AST(抽象语法树),那么语法树中共有几种类型的节点呢?按理说应每种语法规则对应一种类型,例如参数列表,声明语句,普通语句和表达式等都对应一个节点类型,详细可以参见NodeType枚举类型。Parser.c文件是处理与语法树相关的函数,目前来说当中几个函数还没写清楚,TreeNode需要大改一下我估计,过几天也许就明了了。--2018/05/29

  2018/05/30

  没想到只过了一天不到,就完成了语法分析部分,总体上来说还是很简单的。

  有些语法规则导出两种相似的子规则,用专业术语来讲就是就是要做左因子消除,但好像yacc已经代替我们做了这个工作--我猜测它在底下优化了我们混乱的语法规则,包括左递归也解决了。据来说就是if_stmt导出的两种情况,我并不打算在StmtType枚举中添加新的枚举类型来处理,而是利用原有的结构,用nkids来辨别是哪种情况。而在处理var_declaration第二种导出的规则时,原有的结构不够用了,因为我要存储VARIABLE和NUM,很显然一个attr联合体不够用,所以我引入了第二个联合体分别来存储两个值。分别叫attrAattrB。有些时候这样做也无法解决结构上的问题,才不得不用两个枚举类型来解决。现在我也不确定这样做是否多余,毕竟我也是第一次写编译器,但它确实解决了当下的问题。

  我删除了封装yylex()getToken()函数,并解决了注释代码的问题,现在可以支持/**/的段注释和//行注释了。另外,我不想让我代码变得冗余,所以我把构建符号表(Symbol table)的任务放在了语义分析,直接放在语法分析中虽然节约了时间,但实在是难以维护。

  最后根目录下source*.c都成功地进行了语法分析,可能也只是表面上...

  语义分析还没学,翻书去了...

  

猜你喜欢

转载自www.cnblogs.com/yzwqf/p/9114135.html