DOG(5):Dog Language Tools 的实现

今天来开发工具链的第一个前端APP。
整体项目构造:
Dogt.c (main函数) -> Run.c (真正的主函数)解析命令行参数,报错或执行相应的操作
-> 真正的功能

命令行解析

//
// 以下dogc/Run.c L3
int dogt_main(int argc, const char **argv) {
	ProcessType mtp=NULL_T;
	char *ofn = NULL;
	char **fns = NULL;
	int fnp=0;
	const char *now;
	char *s;
	fns = malloc(sizeof(char *) * 100);
	for (int i=1; i<argc; i++) {
		now = argv[i];
		if (!strcmp(now, "-v") || !strcmp(now, "--version")) {
			// 版本信息输出
			DogtGetInfo();
			return 0;
		} else if (!strcmp(now, "-h") || !strcmp(now, "--help")) {
			// 帮助文档输出
			DogtPrintHelp();
			return 0;
		} else if (!strcmp(now, "-c") || !strcmp(now, "--class")) {
			// 生成类定义文档
			mtp = CLASS_T;
		} else if (!strcmp(now, "-m") || !strcmp(now, "--merge")) {
			// 合并类文档
			mtp = MERGE_T;
		} else if (!strcmp(now, "-C") || !strcmp(now, "--check")) {
			// 检查类定义文档语法
			mtp = CHECK_T;
		} else if (!strcmp(now, "-o") || !strcmp(now, "--output")) {
			// 设定输出文件名
			if (ofn) {
				// 已经指定过了
				DogtFatalError("Output file name has already been set.");
				return 1;
			}
			now = argv[++i];
			if (i >= argc) {
				// 会溢出
				DogtFatalError("No enough arguments.");
				return 1;
			}
			ofn = malloc(strlen(now)+1);
			strcpy(ofn, now);
		} else {
			if (now[0] == '-') {
				// 是个参数
				s = malloc(100);
				DogtPrintHelp();
				sprintf(s, "Unknown arg at pos %d: %s.", i, now);
				DogtFatalError(s);
				free(s);
				return 1;
			} else {
				// 是个文件名
				fns[fnp] = malloc(strlen(now)+1);
				strcpy(fns[fnp], now);
				fnp++;
			}
		}
	}
	if (!IsFilesExist(fns, fnp)) {return 1;}
	if (!ofn) {ofn = "d.out";}
	// 检查文件存在性后,再跑相应的操作
	switch (mtp) {
	case CLASS_T:
		return GenFromFiles(fns, fnp, ofn);
	case MERGE_T:
		return CheckClassDeclaredFiles(fns, fnp) | MergeFiles(fns, fnp, ofn);
	case CHECK_T:
		return CheckClassDeclaredFiles(fns, fnp);
	default:
		// 其他功能或者没说明
		DogtPrintHelp();
		return 0;
	}
	return 0;
}

基本程序逻辑一目了然,这里稍微看下DogtPrintHelp()等函数的实现

// dogt/Information.c
void DogtGetInfo(void) {
	printf("DOGT INFO REPORT:\n");
	//...
	printf("AUTHOR: %s\n", DOGT_AUTHOR);
	return;
}

void DogtPrintHelp(void) {
	printf("usage dogt [ops] files...\n");
	printf("ops:\n");
	///...
	printf("\t-o, --output      set output file name.\n");
	return;
}

DogtGetInfo用到的信息宏定义在include/dogt/Information.h

挨个比较参数

L11中,从1索引开始遍历参数(跳过程序名)
p.s.前面几行设定变量,用于储存状态.
如果是"-v" 或 “–version” 就输出版本信息;
如果是"-h" 或 “–help” 就输出帮助文档;
以上两个输出完直接退出(毕竟没谁会把真正的功能跟帮助信息一起输出)

然后再比对是否是功能参数,是的话,就把状态机mtp改成对应的枚举值
(当然,这里暗含着一个机制,同时指定多个功能,会取最后一个(因为后一个把前一个覆盖了))

设定输出文件

因为在函数初始化的时候设定了ofn(输出文件名)为空指针,所以用if判断一下是否已经设定,如果是,就抛一个异常.
p.s. DogtFatalError(const char *) 就是对"报错"和"退出码1"的封装

然后取到后一个参数,并检查是否溢出,也就是参数量够不够,防止dogt -o就没了的情况.

都没问题,申请一块内存,然后复制过去.

万不得已的情况

剩下无非两种,要么是输入文件名,要么是不晓得是啥的参数
如果是后者,那么简单给个报错就跑路;
前者呢,一样的套路,申请内存然后拷贝一份过去.这里注意,输入文件可以有多个.(最多100)

检查文件存在性

都判定后,能执行说明参数没问题,那么检查下输入文件是否存在.功能性函数unsigned char IsFilesExist(char **fns, int count);位于Tools.c.11
就是遍历双指针,然后挨个查,一旦有一个不存在,直接爆掉.

调用功能:编译类文档

所有功能位于dogt/Declare.c
前面几行用于设定全局变量,减少递归函数间的参数传递,提高效率.

struct Class {
	char *name;
	struct methods_t {
		char *name;
		int argc;
		struct methods_t *next;
	} *methods;
	struct members_t {
		char *name;
		struct members_t *next;
	} *members;
};

struct ClassList {
	struct Class *self;
	struct ClassList *next;
};

声明两种链表来存储声明信息.用于后期二次调用

void LinkNameToMemberList(struct members_t *lst, char *name);
void LinkToClassList(struct ClassList *lst, struct Class *p);

用于添加链表(方法类似parser和pcd)

unsigned char GenFromFiles(char **fns, int count, char *opt);
void GenFromFile(char *fn);
struct Class *GenFromClass(struct ClassDefinition *cls);
void GenFromStatementList(struct StatementList *lst);
void GenFromStatement(struct Statement *smt);
void GenFromExpressionList(struct ExpressionList *lst);
void GenFromExpression(struct Expression *exp);

很明显,这几个函数之间互相递归调用(第一个遍历调用第二个,然后第二个调用第三个,然后陷入4-7的循环递归)
(由于取消对继承机制的支持,所以大段注释代码)

void GenFromStatement(struct Statement *smt) {
	if (!smt) {return;}
	switch (smt->tp) {
	case SMTTP_EXP:
		GenFromExpression(smt->smt.exp_s);
		return;
	//...
	case SMTTP_ASSIGN:
		GenFromExpression(smt->smt.as_s->target);
		GenFromExpression(smt->smt.as_s->source);
		return;
	}
}

以此为例,很大的一个switch,对应着各种smt的属性.然后调用表达式生成的函数,进一步深入
GenFromExpression也是类似.

s = exp->exp.attr->name->exp.varName;
if (strcmp(s, "_") && strcmp(s, "__")) {
	GenFromExpression(exp->exp.attr->name);
}
if (exp->exp.attr->exp->tp == EXPTP_ID && 
	!strcmp(exp->exp.attr->exp->exp.varName, "_")) {
	s = exp->exp.attr->name->exp.varName;
	for (struct methods_t *p=__nowcls->methods; p&&p->name; p=p->next) {
		// 此处已保证p->name 不为空
		// 检查下是不是方法
		if (!strcmp(s, p->name)) {return;}
	}
	// 能到这里说明不是方法,允许入队
	LinkNameToMemberList(__nowcls->members, s);
} else {GenFromExpression(exp->exp.attr->exp);}
return;

这里是GenFromExpression().switch.case EXPTP_ATTR的具体实现
逻辑没什么好说的,重点是for循环里,p&&p->name是为了保证p不为空,才能访问p->name.同时保证p->name不为空,strcmp才不会出错.这里利用的是&&运算符的短路性

功能:检查类文档语法

用户可能会自己手写类文档,那么就需要检查下.相当于是给dogc提供接口吧.
还是利用一个自制的短路,只要有一个不对头,马上终止掉并且报错.
挨个对文件调用pcd的接口,没问题就是0的返回值,否则非0.利用这个特性可以实现检查所有文件

功能:合并文档

很简单,挨个打开文件,读取,然后输出到输出文件

附加:hash的显示

由于要与dogc对接,所以这里帮助用户显示每个单词的hash值,方便后续确认运行时错误.

unsigned int Hash(char *str) {
	int p = 16777619;
	int hash = (int)2166136261L;
	for(int i=0;i<strlen(str);i++) {
		hash = (hash ^ str[i]) * p;
	}
	hash += hash << 13;
	hash ^= hash >> 7;
	hash += hash << 3;
	hash ^= hash >> 17;
	hash += hash << 5;
	return hash;
}

这个hash算法(要跟dogc保持同步)

可以看到declare的过程中有些这种函数调用

fprintf(__nowoh, "%-25s0x%x\t%5d\n", s, Hash(s), exp->ln);

即:把s的hash值通过对齐25先输出字符,然后输出一个"0x"表示是十六进制,然后输出hash并对齐,最后是行号.

到这里,编译运行就可以看到dogt运行正常.

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

dtsroy

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值