Day 29 - 回传值

虽然我们实作了好几个方法,但忘记了要处理方法回传值,在 mruby 中处理回传值也是相当简单的,因为编译器在生成指令的时候都已经帮我们处理好对应的机制,只需要将数值复制到正确的位置上就自然而然能够正常的将数值回传。

修改 mrb_func_t

我们原本在 lib/iron/iron.h 定义的 mrb_func_t 是像这样

typedef void (*mrb_func_t)(mrb_state* mrb);

在 mruby 的设计中每个方法的定义都应该回传 mrb_value 作为回传值,也因此我们需要将他修正为

typedef mrb_value (*mrb_func_t)(mrb_state* mrb);

修改方法

因为修改了 mrb_func_t 也因此之前定义的方法都会变成无法使用的状态,所以需要修正方法的实作,这边用 mrb_puts 当作示范。

mrb_value mrb_puts(mrb_state* mrb) {
  int argc = mrb_get_argc(mrb);
  mrb_value* argv = mrb_get_argv(mrb);

  for(int i = 0; i < argc; i++) {
    if(argv[i].tt == MRB_TT_STRING) {
      printf("%s\n", mrb_string(argv[i]));
    } else {
      printf("%d\n", mrb_fixnum(argv[i]));
    }
  }

  return mrb_nil_value();
}

实际上并不困难,只需要修改回传值以及加入 return mrb_nil_value() 回传即可。

在实际的 mruby 实作(CRuby 也是类似)还会传入 mrb_value self 当作参数,大多数时候都会是 return self 但因为我们没有实作物件的概念,因此即使回传 self 也会是 nil

处理回传值

接下来我们需要把 OP_SEND 相关的处理加上修正,让回传值可以正确的被存到寄存器里面。

// lib/iron/vm.c

// ...
      CASE(OP_SEND, BBB) {
        const char* name = (const char*)irep_get(data, IREP_TYPE_SYMBOL, b);
        khiter_t key = kh_get(mt, mrb->mt, name);
        if (key == kh_end(mrb->mt)) {
          DEBUG_LOG("func %s not found", name);
          stack[a] = mrb_nil_value();
        } else {
          // ...

          stack[a] = func(mrb);
        }
        NEXT;
      }
      CASE(OP_SENDB, BBB) {
        const char* name = (const char*)irep_get(data, IREP_TYPE_SYMBOL, b);
        khiter_t key = kh_get(mt, mrb->mt, name);
        if (key == kh_end(mrb->mt)) {
          DEBUG_LOG("func %s not found", name);
          stack[a] = mrb_nil_value();
        } else {
          // ...
          
          stack[a] = func(mrb);
        }
        NEXT;
      }
// ...

实际上就是将找不到方法时直接回传 nil 确保不会抓取到错误的数值,同时将刚刚修改过的 mrb_func_t 回传值储存到原本要用来储存回传值的 stack[a] 上面。

修改 mrb_exec

经过前面实作 Block 的经验,我们会发现一个 Block 的运行单位刚好是 mrb_exec 而呼叫 return 的时候也刚好是从 mrb_exec 离开,因此我们也要将 mrb_exec 的回传值修改为 mrb_value 搭配其他机制。

先把原本在 lib/iron/vm.hint 替换为 mrb_value

mrb_value mrb_exec(mrb_state* mrb, const uint8_t* irep);

然後修改实作将回传值以及用到 return 的地方都用 mrb_value 替代

// lib/iron/vm.c

mrb_value mrb_exec(mrb_state* mrb, const uint8_t* data) {
 // ...
      CASE(OP_BREAK, B) goto L_RETURN;
      CASE(OP_RETURN, B) {
      L_RETURN:
        DEBUG_LOG("%s r[%d]", insn == OP_RETURN ? "return" : "break", a);
        if (mrb->ctx && insn == OP_BREAK) {
          mrb->ctx->block = 0;
        }
        return stack[a];
      }
 
 // ...
 
 return mrb_nil_value();
}

如此一来我们就可以取用回传值来做一些处理了!

加法测试

为了验证前面的修改能够正常使用,我们实做一个简单的 mrb_add 方法来确认。

sum = add(1, 2)
puts sum
mrb_value mrb_add(mrb_state* mrb) {
  mrb_value* argv = mrb_get_argv(mrb);

  int sum = mrb_fixnum(argv[0]) + mrb_fixnum(argv[1]);

  return mrb_fixnum_value(sum);
}

// ...

mrb_define_method(mrb, "add", mrb_add);

调整测试的程序码加入上面两段程序後,运行 pio test 就可以看到 3 被正确的显示在萤幕上。

要注意上面的程序码只是参考不能直接使用

不过这个回传值处理还是非常阳春的,像是「阵列」就无法处理,这些情况就需要未来持续扩充我们的 Ruby VM 实作来支援。


<<:  Arduino 扩充版 W5100 - EEPROM 烧录

>>:  资料分析商业应用与策略管理 #笔记二

【Day 05】 实作 - 设置初始环境於 AWS 建置个人的 WordPress 网站

想了很久要针对哪个主题进行资料分析实作,後来想来想去决定选择最常见的『网站』来进行资料分析的实作,那...

【第三天 - Stack 题目分析】

先简单回顾一下,今天预计分析的题目: Valid Parentheses 昨天问到,如果 ([)] ...

30天学会 Python: Day 12-人生苦短,使用 Python

Python 还有很多不同功能的内建函式,以下列出一些满常用到的 数学相关 abs(x) 回传 x ...

[Day11] CH08:积沙成塔——Array & ArrayList(上)

很快地已经学了十天,今天又是一个新的开始,今天要来认识「阵列」。 阵列(Array)是由同型别的相关...

图的资料结构

3 图的资料结构 今天来介绍我们储存一张图的时候,几种常见的资料结构:相邻矩阵(Adjacency ...