自己写一个 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 子句(循环体) 是 一个 作用域  。

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

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

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

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

大部分 的 语法检查 都在   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 的  Bin\Debug  目录下,  有一个 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 表达式 大括号 的 判断 不会让  编译器 架构 的 关注点 分散,

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

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

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

猜你喜欢

转载自www.cnblogs.com/KSongKing/p/11013210.html