mRuby mruby 源码浏览

pynix · 2014年06月16日 · 最后由 pynix 回复于 2014年06月18日 · 10196 次阅读

说不上源码剖析,就叫浏览吧。

工欲善其事,必先利其器。

安装 ctags,mruby 项目根目录执行ctags -R

vim ctags 命令:

ctrl+]跳转到定义,这个是最常用的。

:ta[gs]列出 tags 栈。

:ts patterntag 搜索。

冰冻三尺,非一日之寒。

阅读源代码前,当然要先构建一遍。项目根目录执行makerake,以默认配置构建。

构建结果在 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_openmrb_close初始化和资源清理。 mrb_load_filemrb_load_file_cxt执行文件,后者带上下文。 mrb_load_stringmrb_load_string_cxt执行字符串。

才子,真有闲!

lz 的 vim 配色真重口味。

#3 楼 @rasefon 默认配色。。。。

大有发展成 mruby hacking guide 之势。^_^

#6 楼 @pynix 既然都做了这么多工作了,可以继续分析下去,做做 Diagram,理一下整个逻辑(解释流程)可能会好一点。我目前看过最漂亮的 C 语言实现的解释器应该是 Lua,云风大神也写过 Lua 的源码分析。

还有,RHG(Ruby Hacking Guide)是一本很好的书,分析地也比较透彻。作者不是简单地向读者介绍如何去分析源码,而是在理解源码的接触上,又自己整理了一下。照这个势头,你完全可以写成 mruby hacking guide。

mruby 的词法、语法分析可以略过,记忆中 Ruby 是拿 yacc 整的,其实也挺麻烦的。我觉得详细分析一下 Ruby 中的数据类型在 C 语言层面上是如何表示的。之前在 RHG 上看到 RString 和 RArray 里面有个 aux 域,感觉有点精妙。

读源码是件乐事,可以从中学到很多东西,之前为了在实现一个 Scheme 解释器的时候,我就参考了 Lua 和 Ruby 的实现代码(Python 方面则是参考一个师兄之前做的讲座有介绍 CPython 的实现),偷学了不少技巧,就比如 Ruby 中对 VALUE 的处理。受用无穷。

楼主加油!

#7 楼 @DeathKing 刚刚看了一下,词法分析是手动写的,语法分析是 yacc(bison),我参考你说的资料整理一下。

#7 楼 再看看 lua 分析。

#7 楼 @DeathKing 风云大神原来是网易游戏的主力啊,进他的博客看了一下,基本都是 lua。

看源码的话,建议先从 Ruby 的对象模型入手,这个看明白之后,所有所谓的什么“元编程”就都没什么神秘了。 然后有兴趣可以再看看 Proc 的实现或你其他感兴的 Class。再就可以看 mruby 的 VM 和 GC 了。至词词法,语法,同意 @DeathKing 的看法,先略过就好。mruby 的词法分析就是从 CRuby 那里拿来的,略微调整了一下。再往后,有时间可以看看 字节码的生成等。。。

#11 楼 @skandhas 大牛要多多科普啊

#11 楼 @skandhas

大学编译原理没学好,看来还是需求驱动学习。

#12 楼 @yukihiro_matz 给跪了!mruby 可是出自你手啊!还是你来更合适!;) #13 楼 @pynix mruby 的源码不算多,一点一点看就可以了,期待你的源码分析笔记。

这几天浏览下来最不满意的就语法分析了。

需要 登录 后方可回复, 如果你还没有账号请 注册新账号