说不上源码剖析,就叫浏览吧。
安装 ctags,mruby 项目根目录执行ctags -R
。
vim ctags 命令:
ctrl+]
跳转到定义,这个是最常用的。
:ta[gs]
列出 tags 栈。
:ts pattern
tag 搜索。
阅读源代码前,当然要先构建一遍。项目根目录执行make
或rake
,以默认配置构建。
构建结果在 build/host/目录下,bin 目录下有三个可执行文件,同时也被链接到了项目根目录的 bin 目录下。
mruby 和 mirb 就和 CRuby 一样。
执行bin/mirb
就可以输入表达式了。
下面正式浏览源代码,先看目录结构。
benchmark --------------------跑分 bin --------------------可执行文件 build --------------------构建产物 build_config.rb --------------------构建配置,包括是否启用特性和 gems。 doc --------------------文档 examples --------------------示例:c 扩展和 gem 示例。 include --------------------头文件 Makefile minirake -------------------- rake
mrbgems --------------------gems mrblib --------------------stdlib(ruby 代码,直接编译成字节码嵌入 c 文件) Rakefile src --------------------核心代码
test --------------------测试
项目根目录打开 vim,以便正确找到 tags 文件,:ts main
。
我们都知道,通常情况下 c 语言的入口都是 main,我们拿 main 开刀。从图从可以看到果然不止一个 main 函数。
四个有正常参数的看文件名就知道了。中间还有一个乱入的。
从 mruby 的 main 函数开始吧。输入数字,跳转到文件。
int
main(int argc, char **argv)
{
mrb_state *mrb = mrb_open();
int n = -1;
int i;
struct _args args;
mrb_value ARGV;
mrbc_context *c;
mrb_value v;
if (mrb == NULL) {
fputs("Invalid mrb_state, exiting mruby\n", stderr);
return EXIT_FAILURE;
}
n = parse_args(mrb, argc, argv, &args);
if (n == EXIT_FAILURE || (args.cmdline == NULL && args.rfp == NULL)) {
cleanup(mrb, &args);
usage(argv[0]);
return n;
}
ARGV = mrb_ary_new_capa(mrb, args.argc);
for (i = 0; i < args.argc; i++) {
mrb_ary_push(mrb, ARGV, mrb_str_new(mrb, args.argv[i], strlen(args.argv[i])));
}
mrb_define_global_const(mrb, "ARGV", ARGV);
c = mrbc_context_new(mrb);
if (args.verbose)
c->dump_result = 1;
if (args.check_syntax)
c->no_exec = 1;
if (args.mrbfile) {
v = mrb_load_irep_file_cxt(mrb, args.rfp, c);
}
else {
mrb_sym zero_sym = mrb_intern_lit(mrb, "$0");
if (args.rfp) {
char *cmdline;
cmdline = args.cmdline ? args.cmdline : "-";
mrbc_filename(mrb, c, cmdline);
mrb_gv_set(mrb, zero_sym, mrb_str_new_cstr(mrb, cmdline));
v = mrb_load_file_cxt(mrb, args.rfp, c);
}
else {
mrbc_filename(mrb, c, "-e");
mrb_gv_set(mrb, zero_sym, mrb_str_new(mrb, "-e", 2));
v = mrb_load_string_cxt(mrb, args.cmdline, c);
}
}
mrbc_context_free(mrb, c);
if (mrb->exc) {
if (!mrb_undef_p(v)) {
mrb_print_error(mrb);
}
n = -1;
}
else if (args.check_syntax) {
printf("Syntax OK\n");
}
cleanup(mrb, &args);
return n == 0 ? EXIT_SUCCESS : EXIT_FAILURE;
}
大致流程:
初始化 mruby 执行环境 解析命令行参数 根据参数执行相应的动作 清理退出
main 函数一开始的mrb_open
就是初始化 mruby 执行环境。
进入函数,来到mrb_open_allocf
,看来只是一个壳,继续。
进入后往下走,mrb_init_core
,这个函数很可疑哦,必须进去探个究竟。
void
mrb_init_core(mrb_state *mrb)
{
mrb_init_symtbl(mrb); DONE;
mrb_init_class(mrb); DONE;
mrb_init_object(mrb); DONE;
mrb_init_kernel(mrb); DONE;
mrb_init_comparable(mrb); DONE;
mrb_init_enumerable(mrb); DONE;
mrb_init_symbol(mrb); DONE;
mrb_init_exception(mrb); DONE;
mrb_init_proc(mrb); DONE;
mrb_init_string(mrb); DONE;
mrb_init_array(mrb); DONE;
mrb_init_hash(mrb); DONE;
mrb_init_numeric(mrb); DONE;
mrb_init_range(mrb); DONE;
mrb_init_gc(mrb); DONE;
mrb_init_mrblib(mrb); DONE;
#ifndef DISABLE_GEMS
mrb_init_mrbgems(mrb); DONE;
#endif
}
初始化符号表类型系统。
mrb_init_mrblib
,我对这个函数很感兴趣,因为 mrblib 里面的代码是 ruby 写的,被编译成了字节码,这个函数是不是要执行字节码了呢?果断进去。
mrb_load_irep
从这个函数名或许可以看出点什么?keep going。
mrb_load_irep_cxt
,往下走。
有个mrb_context_run
,函数名有 run,可能真的要开始“跑“了。
初极狭,才通人。复行数十步,豁然开朗。土地平旷,屋舍俨(yǎn)然,有良田,美池,桑竹之属。阡陌(qiān mò)交通,鸡犬相闻。其中往来种作,男女衣著(zhuó),悉如外人。黄发垂髫(tiáo),并怡然自乐。
一个大大的 switch 语句呈现在我们眼前,仿佛进入了世外桃源。字节码在这里有序执行,男女衣著,怡然自乐。
在这里吃喝玩乐数天,该出去了。
ctrl+t
一路返回到 main 函数。
关键代码:
c = mrbc_context_new(mrb);
v = mrb_load_file_cxt(mrb, args.rfp, c);
v = mrb_load_string_cxt(mrb, args.cmdline, c);
带上太守和村民寻找桃花源。
两个函数跟进,最终都到达了mrb_context_run
。
以上就是大致的执行流程,我没有深入语法分析部分,那里面的水实在太深,涉及编译原理,可以另外开篇了。
以前读过一篇 python 源码分析,看完 mruby 的代码,感觉和 python 很像,或许大部分解释器都是这个流程,只是我没看过而已。
API:
mrb_open
,mrb_close
初始化和资源清理。
mrb_load_file
,mrb_load_file_cxt
执行文件,后者带上下文。
mrb_load_string
,mrb_load_string_cxt
执行字符串。