c51编译器编写日记
0. 前言
之前在做c51汇编编译器的时候就在想, 啥时候也做一个c语言编译器, 然后就挖坑了.
正好最近发现一个项目(MAZUCC)的教学目的的pc端编译器, 语法分析写的很清晰,
就尝试写一下c51的线性寄存器分配模型, 发现似乎可行, 就开启了填坑的工作.
本日志算是记录, 算是督促.
欢迎star: https://github.com/peitianyu/c51cc
大家测试中的问题可以贴在帖子下方
1. 测试并梳理标准(MAZUCC)所缺语法
一、表达式与运算符
一元:
有:+ - ++ -- * & !
无:sizeof (type)cast typeof ~
二元:
有:+ - * / % << >> < > == & | && || =
无:^ . -> <<= >>= &= |= ^= >= <= != += -= *= /= %=
三元:
有:?:
常量 & 字面量
有:十进制, 字符, 字符串, 十进制浮点
无:十六进制(0x12), 不支持二进制(0b00010002), 八进制整型, 不支持后缀{L, f等}
二、声明与类型系统
基础类型
有:char int float double
无:signed/unsigned
聚合类型
有:结构体变量, 联合体, 一维/多维数组, 指针
无:结构体, 联合体变量不支类型声明, 不支持位域, 不支持{}初始化
修饰符(全部禁用)
存储类:auto register static extern typedef
类型限定:const volatile restrict _Atomic
函数限定:inline _Noreturn
三、语句与控制流
有:if else for return
无:
else if(语法可写,但按嵌套 if-else 理解)
while do-while switch/case/default break continue goto
四、函数
有:
普通函数定义、递归、函数指针
无:
void foo(void){} 空形参列表写法(必须空括号)
可变参数 ...、<stdarg.h>
inline、_Noreturn、函数原型默认参数提升规则外的任何扩展
五、预处理 & 编译单元
有:无(整个预处理器被移除)
无:
六、标准库
有:
无(整个标准库不存在)
七、存储模型与生命周期
有:
全局变量(程序整个生命期)
局部变量(进入复合语句时创建,退出即销毁)
指针算术与任意层级指针
无:
静态局部变量、static 全局、extern 跨文件链接
动态内存 malloc/free、_Thread_local、线程存储期
八、单片机专属扩展
无: sfr sfr16 sbit bit data xdata idata bdata code reentrant interrupt using
2. 搭建设计测试框架
之前我一直希望有一个类似于rust的 cargo run与 cargo test的测试框架, 正好之前实现过一个c_test工具.
但发现还是没那么好用, 且不支持tcc编译, 查资料发现c语言中有一个 section, 可以直接将变量在编译阶段就预设好, 这就很nice了.
于是就有了接下来的测试框架, 代码非常少, 单头文件, 宏定义实现, 支持tcc与gcc编译. 非常符合我的审美了.
/* minitest.h */
#ifndef MINITEST_H
#define MINITEST_H
#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#ifdef MINITEST_IMPLEMENTATION
#ifdef __TINYC__
__asm__(".global __start_testsec\n"
"__start_testsec = .\n"
".global __stop_testsec\n"
"__stop_testsec = .\n");
#undef __attribute__
#endif
extern void *__start_testsec;
extern void *__stop_testsec;
typedef void (*TestFn)(void);
typedef struct { const char *name; TestFn fn; } Test;
extern int g_argc;
extern char **g_argv;
#define TEST(suite, name) \
static void suite##_##name(void); \
static const Test __t_##suite##name \
__attribute__((section("testsec"))) = { \
#suite "." #name, suite##_##name }; \
static void suite##_##name(void)
#define ASSERT(cond) do { if (!(cond)) { printf("FAIL %s:%d %s\n", __FILE__, __LINE__, #cond); exit(1); } } while (0)
#define ASSERT_TRUE(x) ASSERT(x)
#define ASSERT_EQ(a, b) ASSERT((a) == (b))
#define ASSERT_STREQ(a,b) ASSERT(strcmp((a),(b)) == 0)
#define RUN_ALL_TESTS(argc, argv) \
do { \
g_argc = (argc); \
g_argv = (argv); \
Test *b = (Test*)&__start_testsec, *e = (Test*)&__stop_testsec; \
size_t n = e - b; \
if (!n) { puts("No tests"); break; } \
printf("\n\033[36m===== Tests =====\033[0m\n"); \
for (size_t i = 0; i < n; ++i) printf("\033[33m%2zu\033[0m : %s\n", i + 1, b[i].name); \
printf("\033[35m# (0=all, ENTER=quit): \033[0m"); fflush(stdout); \
int c = getchar(); \
if (c == '\n' || c == EOF) break; \
ungetc(c, stdin); \
int k; if (scanf("%d", &k) != 1) break; \
while (getchar() != '\n'); /* 吃掉行尾 */ \
if (k < 0 || (size_t)k > n) { puts("\033[31mBad#\033[0m"); break; } \
for (size_t i = (k ? k - 1 : 0), lim = (k ? i + 1 : n); i < lim; ++i) { \
printf("\033[32m[ RUN ]\033[0m %s\n", b[i].name); b[i].fn(); \
printf("\033[32m[ OK ]\033[0m %s\n", b[i].name); \
} \
} while (0)
#else
#define ASSERT(cond) do { if (!(cond)) { printf("FAIL %s:%d %s\n", __FILE__, __LINE__, #cond); exit(1); } } while (0)
#define ASSERT_TRUE(x) ASSERT(x)
#define ASSERT_EQ(a, b) ASSERT((a) == (b))
#define ASSERT_STREQ(a,b) ASSERT(strcmp((a),(b)) == 0)
#define TEST(suite, name) static void suite##_##name(void)
#define RUN_ALL_TESTS(argc, argv) do{}while(0)
#endif
#endif /* MINITEST_H */
/* main.c */
#ifdef MINITEST_IMPLEMENTATION
#include "core/minitest.h"
int g_argc;
char **g_argv;
void main(int argc, char **argv) {
RUN_ALL_TESTS(argc, argv);
}
#else
#include "stdio.h"
int main() {
printf("hello main\n");
return 0;
}
#endif
/* run.sh */
tcc main.c core/*.c -run -DMINITEST_IMPLEMENTATION