自己写一个 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 的  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 表达式 大括号 的 判断 不会让  编译器 架构 的 关注点 分散,

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

 

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

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

 

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

 

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

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

 

 

 

 

 

 

 

2022-04-07 :

接下来 要 继续 完成 InnerC,    之前 的 版本 见   https://github.com/kelin-xycs/InnerC/tree/f546c7e275867ec280ad495eb235bb39536808f5

 

本来 要打个 Tag,  “First-Version”  ,    但 找了一下 也 找不到 怎么 打 Tag,   算了,  不打了  。

 

 

 

 

 

2023-01-31 :

2022 年 4 月 初 时候 实现了

1    for 语句

2    作用域  (父作用域  / 子作用域)

3    变量定义检查  (检查变量是否存在)

4    Cast,  类型转换,   比如   ( B ) a  ,   只是 语法 上 支持,  实际上 并没有作 类型检查  。      忘了 是 这次做的,  还是 以前做的  。

 

计划还要实现

1    三元运算符   ?  :    ,     其实 也考虑 用 一个 函数 来 代替 三元运算符,   比如   ?  ( a, b, c  )   ,     但 被 QQ 群(K 开源联盟,  我是 群主) 里 的 一个 专家网友 大骂  不专业  、不入流  、幼稚  、  在 国际 上 被 笑话  。      但 我 怎么 觉得 还 挺 骄傲 呢  ?   在 国际 上  。

2    try  catch

3    名字空间,   既然 有 名字空间,    当然 就 要 取消 头文件 和  include,    include 关键字 可以 另作它用  。

4    宏结束符   #end  ,    InnerC 里 的 宏定义 是 

      #define  名字

         内容

      #end

显而易见,      InnerC  允许 宏 的 内容 换行   。

5    泛型,    就是 一个 简单 的 模板,   支持 模板类 嵌套,   即  泛型嵌套,   比如    A < B < C > > 

 

要不要 实现 内联  inline  ?    呵呵,    这个 问题 留给大家   。

 

总之,      InnerC  是 让  C 现代化   。

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

posted on 2019-06-12 22:27  凯特琳  阅读(1194)  评论(0编辑  收藏  举报

导航