zoukankan      html  css  js  c++  java
  • 自己写一个 C 语言 编译器 InnerC

    InnerC  用于 ILBC,   我现在把它独立一个版本出来,   项目地址:

     

    https://github.com/kelin-xycs/InnerC              ,

     

    InnerC 是一个   C 语言 编译器,  最初的 目的 是 作为  ILBC  的  中间语言 编译器  用于编译 C 中间语言  。

    有关 ILBC ,   见 《ILBC 规范》   https://www.cnblogs.com/KSongKing/p/10354824.html        。

     

    目前 InnerC 已实现的部分 只包含 语法分析 和 语法检查, 不包含 生成目标代码 和 链接  。

     

    目前 InnerC 支持     全局变量 函数 结构体 数组 指针 函数指针, int float char ,     四则运算, 大于小于不等于 比较, 与或非逻辑运算,

    if 语句, while 语句,  不支持 for 语句, 主要是 懒得写了,烦  。  以后可以加上  。

    支持 return break continue  语句  。

    支持 作用域,  比如 函数体 是一个 作用域, 函数形参 是一个 作用域,  if 子句 和 while 子句(循环体) 是 一个 作用域  。

    不支持 ++  --  +=  -=,    也是 没时间写 。 以后可以加上  。

    不支持     三元运算符     ?   :    ,        三元运算符 的 规则 和 一般的 运算符 有所不同,   要 额外 的 一些 语法分析 逻辑  来 处理  。  以后可以加上  。

     

    语法检查 的 部分  只 粗略 的 实现了 检查 变量是否已声明,  是否在 上级作用域 中声明了同名的变量, 只写了代码,没有测试  。

    另外还实现了 函数名 和 结构体名 的 命名检查,  就是 应该由 下划线字母数字 组成 且 以 下划线字母 开头,以及 不能 和 关键字 相同  。

     

    命名检查 和 语法检查 是 分开的,因为 在 语法检查 里 检查 函数名 和 结构体名 是否存在,  所以 先 进行 命名检查  。

    目前 命名检查 包含在 文本解析(Parse)过程 中 。

     

    大部分 的 语法检查 都在      I_C_Member.类型和语法检查()     方法   里  实现   。

    只要 去 实现     I_C_Member   接口 的   类型和语法检查()      方法     就行了  。

     

    所有的 语法成员 都 继承了    I_C_Member 接口 ,   包括    变量声明  结构体  函数  作用域  各种语句  各种表达式  。

     

    所以这个架构是 很清晰 的,   完善剩余的部分 只是 工作量 的 问题  。

     

    这里把   类型和语法检查   要做 的 工作 大概 列一下  :


    检查上级作用域中是否已定义了同名的变量

    变量 参数 返回值 字段 的 类型 是否正确,比如 是否是 int float 等基础类型或结构体

    是否使用了 未定义 的 变量 参数 字段

    变量不能在声明前使用

    运算符两边的表达式的类型是否匹配

    Cast 是否合法

    函数返回值的类型和声明的返回类型是否一致

    是否使用了 未定义 的 函数 和 结构体

    数组声明 的 维度长度 只能是 常量 或者 常量表达式,如果用 常量 初始化数组,可以不用声明维度长度,但这好像只适用于 一维数组

    全局变量 初始化 只能用 常量 或者 常量表达式

    因为 大部分 的 语法检查 都和 类型 有关,所以归到一起称为 “类型和语法检查”

     

    这些内容 在 代码 的 注释 里有写 。

     

    除了以上,还有 2 个 语法检查 是 在   类型和语法检查   之后 独立 进行的,分别是 :

     

    检查函数内所有路径都有返回值

    检查结构体不能循环包含

     

    这个 流程 在 代码 里 可以很清楚的 看到  。

     

    可以在 解决方案 中的 InnerC_Demo 项目 看到 Demo,  这是一个 WinForm 项目, 运行 InnerC_Demo.exe,   指定要编译 的 C 源文件,  点击 “测试” 按钮, 如果没有语法错误,  就会 把 C 源文件 编译为 语法成员树,   并 将 语法成员树 逆向 还原 为 C 源代码,   还原后 的 C 源代码 保存在 另外一个 文件里,  这个文件的文件名 是 原文件名 加上  “.reverse.c”   ,   比如 源文件名 是 “a.c”,   还原后的 文件名 是 “a.c.reverse.c”   。

     

    在 InnerC_Demo 的  BinDebug  目录下,  有一个 Test.c ,    运行 InnerC_Demo.exe  可以 编译 Test.c  观察 演示效果 。

     

    这次对 C 语法 有一点 修改,就是 C 语言 是用  大括号 如  { 1, 2, 3, 4 }  表示 一个 数组常量,  但是这让 InnerC  的 编译器 变得复杂 。

    因为 大括号 是用来表示 一个 代码块,比如 函数体, 结构体,  或者 if 子句, 或者 while 子句(循环体),

    用  大括号 表示  数组常量  会让   第一层解析 划分 函数 和 结构体 的 大括号块 变得 麻烦  。

    为了 维持 编译器 的 简单清晰,  我决定 做出一个 改革,

    改用 中括号 来表示 数组常量,如   [ 1, 2, 3, 4 ]    ,    结果很爽      。  啊哈哈哈    。

    我觉得  发明 C 语言  的 前辈  可能有 大括号  偏爱癖好   ,      要不就是 可能 看到 当时 其它语言 里 用 中括号 表示 数组 觉得 不爽 。

     

    未来  D# 也会沿用 这个 做法,   D#  的 编译器 可以在  InnerC 的 基础上 扩展而来 。

     

    在  D#  中 有 Lambda 表达式, 这样 是不是 仍然 要 增加 对 Lambda 表达式 的 判断?

    是的,  但是,  Lambda 表达式 是一个  明显的 主要的     需求,     而且 可以根据 大括号 前面 是否有   ()=>  操作符  来 明确的 判断 大括号 是否是 Lambda 表达式,

    在 划分 方法  的 大括号块 时 加入 是否是 Lambda 表达式 大括号 的 判断 不会让  编译器 架构 的 关注点 分散,

    而 数组常量 是一个 很弱 的 需求,  在 划分 函数 结构体 大括号块  时  加入 是否是 数组常量 大括号 的 判断 会让 编译器 架构 的 关注点 分散 。

     

    咦?   大家可能会问,   在 函数 和 结构体 外 哪里来的 数组常量?   全局变量 啊,  全局变量 的 初始化 可能会用 这种 大括号数组常量  。 

    如果 是 在 函数 和 结构体 内部,  其实 没什么问题  。

     

    InnerC   还有另外一个 意义,  就是   可以作为 编译器 的 范例 和 内核,  让后人可以容易的   学习了解 编译器 以及 在 这个基础 上 改写 和 开发 新的 编译器  。

     

    另外,  根据 InnerC 的原理, 其实 可以写一个 正则表达式 引擎  。  正则表达式 本身 就是一个 描述规则的文本,需要 文本解析, 解析得到 规则,  把 规则 保存在 字典(Hash 表) 里,  根据 规则 对 目标字符串 进行 匹配  。

    这些用 InnerC 的 文本解析 方法 都可以实现  。

     

     

     

     

     

  • 相关阅读:
    Android中的httpclient框架发送get请求
    成员函数的重载&&隐藏&&覆盖
    子墨庖丁Android的ActionBar源代码分析 (一)实例化
    Hadoop2.x介绍与源代码编译
    NFS 服务器的配置
    tftp 服务器的配置
    LINUX内核及应用程序移植工作
    u-boot 移植工作目录
    Linux 下工作用户及环境
    zless
  • 原文地址:https://www.cnblogs.com/KSongKing/p/11013210.html
Copyright © 2011-2022 走看看