目标组件:
已完成组件:
目录结构:
./temp/ 编译器生成的临时文件,例如obj等.
其中,./Arsenal/可支持所有目标平台,./Tools下的GrammarDesigner,仅支持win x86 X64.本工程所有生成的二进制文件都放在./Arsenal/binary下。所有引用到MFC的DLL版本都需要安装vs2k8发行包:VS2008_Redistributable_Package
用法:
词法分析部分:
语法分析部分:
目 标: 在最开始我并没有想把这两样东西通用化,仅仅想做一个比较精简的基于递归下降技术的类C解释器(应该会去掉很多晦涩的C语法支持),但是在实践过程中,我 发现需要一些工具,例如便于实时设计和修正文法以及观测此文法接受的语言和其具体语法树(包括错误处理之后的)等等,因此才决定实现一个可配置的LR分析 器。另外,一个被常问到的问题是,自己写一个类似yacc的库有什么用处?我只能说,这个东西可以轻易的改装成任何我需要的东西,不论是对UI的友好程度 还是线程安全性以及其执行时的解释性质,至少很难拿yacc做个像样的设计工具出来。
实现:
错 误处理:我采用的是和yacc相当类似的一种技术–error token,首先error作为保留终结符,当有错误发生时,直接检索当前Action Table是否有可以移入error,如果有,则移入,否则,向上pop_stack,直到可以接受终结符error为止,此时parser状态进入 error状态,在此状态下任何非法的输入符号均被丢弃,直到移入了一个在error上合法的符号(这里并非是终结符,请看例子),此时恢复到正常状态。 如果任何错误发生时直到parser-stack空为止都不能接受终结符error,则分析失败,否则带error的产生式会被当作正常产生式处理,可以 在规约时处理这条错误产生式。例如A -> error ‘;’;当有任何错误发生时,parser会移入error直到移入’;’后才会恢复到正常状态。同样的道理, A -> error TERM; TERM -> ‘;’;同样可以接受,实际上和上面的error -> ‘;’相同,只不过先做一次’;’到TERM的规约动作,当然 A->error同样可以接受,只是错误状态被传递到A之后的符号,也可以在分析中清除Error状态,例如在某个ActionHandler中调 用PSR_ClearError(…);
优先级,结合性:同样和yacc的准则类似,优先选择移入,每条产生式的优先级默认是其最右终结符号,也可以单独指定产生式的优先级以及结合性。例如:
%left ‘+’, ‘-‘;
%left ‘*’, ‘/’, ‘%’;
%right UMINUS;
E -> E ‘+’ E | E ‘-‘ E | E ‘*’ E | E ‘/’ E | E ‘%’ E | ‘-‘ E %prec UMINUS | ‘(‘ E ‘)’;
这里,配置起来的 前两条产生式的优先级结合性是其最右终结符,也就是'+' '-', 之后三条是'*' '/' '%' ,在 E -> '-' E %prec UMINUS 中,其优先级结合性取自符号UMINUS,而最后一个 E->'(' E')' 的优先级为默认的,默认为0和无结合性。
这里,配置起来的 前两条产生式的优先级结合性是其最右终结符,也就是'+' '-', 之后三条是'*' '/' '%' ,在
'+' '-'
'*' '/' '%'
E -> '-' E %prec UMINUS
中,其优先级结合性取自符号UMINUS,而最后一个
E->'(' E')'
的优先级为默认的,默认为0和无结合性。
实例:先简单说下具体用法,因为采用的是表驱动的Shift-Reduce Parser,所以Parser的接口提供了一个名为PSR_AddToken的函数,每次调用处理一个词法值,直到移入它,此函数返回,当EOI被接受 后,此Parser状态为接受状态,所以实用中是词法分析器调用语法分析器。关于语法树的构建实际上是靠针对每条产生式注册一个相关的Action- Handler来完成的,详情请参考实例:./Arsenal/Tools/grammar_config.c,里面实现一个完整的基于Arsenal的 语法配置程序.一个完整的应用Arsenal的例子是./Tools/GrammarDesigner/,一个基于MFC的文法设计工具,可以用其观测大 部分文法的具体语法树。基于时间关系,文档也不可能写的相当详细,而且代码还处于不断的修改中,所以有人对其有兴趣的话可以联系我。
错 误报告: 这是个不太好设计的部分,暂时权宜之计是在AR_Init中注册统一的error以及print handler,并由统一的AR_error和AR_printf调用,在AR_printf假设UI共享同一个终端,另外每个子模块例如lex_t被创 建出来时需要注册一个ioctx_t;例如:
typedef void (AR_STDCALL AR_error_func_t)(int_t level, const wchar_t msg, void ctx); typedef void (AR_STDCALL AR_print_func_t)(const wchar_t msg, void ctx);
typedef struct __arsenal_io_context_tag { AR_error_func_t on_error; AR_print_func_t on_print; void *ctx; }arIOCtx_t;
在需要报告错误时,lex_t内部会调用例如: AR_error_ctx(io_ctx, AR_ERR_FATAL, "error = %ls", err_msg); 等等,暂时限于我对此类软件的理解,只能如此设计。
在需要报告错误时,lex_t内部会调用例如:
AR_error_ctx(io_ctx, AR_ERR_FATAL, "error = %ls", err_msg);
等等,暂时限于我对此类软件的理解,只能如此设计。
移植:
一些问题:
联系方式: