Commit 927615e1 authored by dearblue's avatar dearblue

Added other methods for `Binding`

- Added to `mruby-binding-core`
  - `Binding#local_variable_defined?`
  - `Binding#local_variable_get`
  - `Binding#local_variable_set`
  - `Binding#local_variables`
  - `Binding#receiver`
  - `Binding#source_location`
  - `Binding#inspect`
- Added to `mruby-proc-binding`
  - `Proc#binding`

The reason for separating `Proc#binding` is that core-mrbgems has a method that returns a closure object to minimize possible problems with being able to manipulate internal variables.
By separating it as different mrbgem, each user can judge this problem and incorporate it arbitrarily.
parent 792f6ac6
MRuby::Gem::Specification.new('mruby-binding-core') do |spec|
spec.license = 'MIT'
spec.author = 'mruby developers'
spec.summary = 'Binding class'
spec.summary = 'Binding class (core features only)'
spec.add_test_dependency('mruby-proc-ext', :core => 'mruby-proc-ext')
end
......@@ -4,8 +4,160 @@
#include <mruby/proc.h>
#include <mruby/variable.h>
#include <mruby/presym.h>
#include <mruby/opcode.h>
#include <mruby/debug.h>
void mrb_proc_merge_lvar(mrb_state *mrb, mrb_irep *irep, struct REnv *env, int num, const mrb_sym *lv, const mrb_value *stack);
mrb_value mrb_proc_local_variables(mrb_state *mrb, const struct RProc *proc);
const struct RProc *mrb_proc_get_caller(mrb_state *mrb, struct REnv **env);
static mrb_int
binding_extract_pc(mrb_state *mrb, mrb_value binding)
{
mrb_value obj = mrb_iv_get(mrb, binding, MRB_SYM(pc));
if (mrb_nil_p(obj)) {
return -1;
}
else {
mrb_check_type(mrb, obj, MRB_TT_INTEGER);
return mrb_int(mrb, obj);
}
}
static const struct RProc *
binding_extract_proc(mrb_state *mrb, mrb_value binding)
{
mrb_value obj = mrb_iv_get(mrb, binding, MRB_SYM(proc));
mrb_check_type(mrb, obj, MRB_TT_PROC);
return mrb_proc_ptr(obj);
}
static struct REnv *
binding_extract_env(mrb_state *mrb, mrb_value binding)
{
mrb_value obj = mrb_iv_get(mrb, binding, MRB_SYM(env));
if (mrb_nil_p(obj)) {
return NULL;
}
else {
mrb_check_type(mrb, obj, MRB_TT_ENV);
return (struct REnv *)mrb_obj_ptr(obj);
}
}
static void
binding_local_variable_name_check(mrb_state *mrb, mrb_sym id)
{
if (id == 0) {
badname:
mrb_raisef(mrb, E_NAME_ERROR, "wrong local variable name %!n for binding", id);
}
mrb_int len;
const char *name = mrb_sym_name_len(mrb, id, &len);
if (len == 0) {
goto badname;
}
if (ISASCII(*name) && !(*name == '_' || ISLOWER(*name))) {
goto badname;
}
len--;
name++;
for (; len > 0; len--, name++) {
if (ISASCII(*name) && !(*name == '_' || ISALNUM(*name))) {
goto badname;
}
}
}
static mrb_value *
binding_local_variable_search(mrb_state *mrb, const struct RProc *proc, struct REnv *env, mrb_sym varname)
{
binding_local_variable_name_check(mrb, varname);
while (proc) {
if (MRB_PROC_CFUNC_P(proc)) break;
const mrb_irep *irep = proc->body.irep;
const mrb_sym *lv;
if (irep && (lv = irep->lv)) {
for (int i = 0; i + 1 < irep->nlocals; i++, lv++) {
if (varname == *lv) {
return (env && MRB_ENV_LEN(env) > i) ? &env->stack[i + 1] : NULL;
}
}
}
if (MRB_PROC_SCOPE_P(proc)) break;
env = MRB_PROC_ENV(proc);
proc = proc->upper;
}
return NULL;
}
/*
* call-seq:
* local_variable_defined?(symbol) -> bool
*/
static mrb_value
binding_local_variable_defined_p(mrb_state *mrb, mrb_value self)
{
mrb_sym varname;
mrb_get_args(mrb, "n", &varname);
const struct RProc *proc = binding_extract_proc(mrb, self);
struct REnv *env = binding_extract_env(mrb, self);
mrb_value *e = binding_local_variable_search(mrb, proc, env, varname);
if (e) {
return mrb_true_value();
}
else {
return mrb_false_value();
}
}
/*
* call-seq:
* local_variable_get(symbol) -> object
*/
static mrb_value
binding_local_variable_get(mrb_state *mrb, mrb_value self)
{
mrb_sym varname;
mrb_get_args(mrb, "n", &varname);
const struct RProc *proc = binding_extract_proc(mrb, self);
struct REnv *env = binding_extract_env(mrb, self);
mrb_value *e = binding_local_variable_search(mrb, proc, env, varname);
if (!e) {
mrb_raisef(mrb, E_NAME_ERROR, "local variable %!n is not defined", varname);
}
return *e;
}
static mrb_value
binding_local_variable_set(mrb_state *mrb, mrb_value self)
{
mrb_sym varname;
mrb_value obj;
mrb_get_args(mrb, "no", &varname, &obj);
const struct RProc *proc = binding_extract_proc(mrb, self);
struct REnv *env = binding_extract_env(mrb, self);
mrb_value *e = binding_local_variable_search(mrb, proc, env, varname);
if (e) {
*e = obj;
}
else {
mrb_proc_merge_lvar(mrb, (mrb_irep*)proc->body.irep, env, 1, &varname, &obj);
}
return obj;
}
static mrb_value
binding_local_variables(mrb_state *mrb, mrb_value self)
......@@ -14,19 +166,119 @@ binding_local_variables(mrb_state *mrb, mrb_value self)
return mrb_proc_local_variables(mrb, proc);
}
const struct RProc *mrb_proc_get_caller(mrb_state *mrb, struct REnv **env);
static mrb_value
binding_receiver(mrb_state *mrb, mrb_value self)
{
return mrb_iv_get(mrb, self, MRB_SYM(recv));
}
/*
* call-seq:
* source_location -> [String, Integer]
*/
static mrb_value
binding_source_location(mrb_state *mrb, mrb_value self)
{
if (mrb_iv_defined(mrb, self, MRB_SYM(source_location))) {
return mrb_iv_get(mrb, self, MRB_SYM(source_location));
}
mrb_value srcloc;
const struct RProc *proc = binding_extract_proc(mrb, self);
if (!proc || MRB_PROC_CFUNC_P(proc) ||
!proc->upper || MRB_PROC_CFUNC_P(proc->upper)) {
srcloc = mrb_nil_value();
goto cache_source_location;
}
{
const mrb_irep *irep = proc->upper->body.irep;
mrb_int pc = binding_extract_pc(mrb, self);
if (pc < 0) {
srcloc = mrb_nil_value();
}
else {
const char *fname = mrb_debug_get_filename(mrb, irep, pc);
mrb_int fline = mrb_debug_get_line(mrb, irep, pc);
if (fname && fline >= 0) {
srcloc = mrb_assoc_new(mrb, mrb_str_new_cstr(mrb, fname), mrb_fixnum_value(fline));
}
else {
srcloc = mrb_nil_value();
}
}
}
cache_source_location:
if (!mrb_frozen_p(mrb_obj_ptr(self))) {
mrb_iv_set(mrb, self, MRB_SYM(source_location), srcloc);
}
return srcloc;
}
mrb_value
mrb_binding_alloc(mrb_state *mrb)
{
struct RObject *obj = (struct RObject*)mrb_obj_alloc(mrb, MRB_TT_OBJECT, mrb_class_get(mrb, "Binding"));
return mrb_obj_value(obj);
}
struct RProc*
mrb_binding_wrap_lvspace(mrb_state *mrb, const struct RProc *proc, struct REnv **envp)
{
/*
* local variable space: It is a space to hold the top-level variable of
* binding.eval and binding.local_variable_set.
*/
static const mrb_code iseq_dummy[] = { OP_RETURN, 0 };
struct RProc *lvspace = (struct RProc*)mrb_obj_alloc(mrb, MRB_TT_PROC, mrb->proc_class);
mrb_irep *irep = mrb_add_irep(mrb);
irep->flags = MRB_ISEQ_NO_FREE;
irep->iseq = iseq_dummy;
irep->ilen = sizeof(iseq_dummy) / sizeof(iseq_dummy[0]);
irep->lv = (mrb_sym*)mrb_calloc(mrb, 1, sizeof(mrb_sym)); /* initial allocation for dummy */
irep->nlocals = 1;
irep->nregs = 1;
lvspace->body.irep = irep;
lvspace->upper = proc;
if (*envp) {
lvspace->e.env = *envp;
lvspace->flags |= MRB_PROC_ENVSET;
}
*envp = (struct REnv*)mrb_obj_alloc(mrb, MRB_TT_ENV, NULL);
(*envp)->stack = (mrb_value*)mrb_calloc(mrb, 1, sizeof(mrb_value));
(*envp)->stack[0] = lvspace->e.env ? lvspace->e.env->stack[0] : mrb_nil_value();
(*envp)->cxt = lvspace->e.env ? lvspace->e.env->cxt : mrb->c;
(*envp)->mid = 0;
(*envp)->flags = MRB_ENV_CLOSED | MRB_ENV_HEAPED;
MRB_ENV_SET_LEN(*envp, 1);
return lvspace;
}
static mrb_value
mrb_f_binding(mrb_state *mrb, mrb_value self)
{
struct RObject *obj;
mrb_value binding;
struct RProc *proc;
struct REnv *env;
obj = (struct RObject*)mrb_obj_alloc(mrb, MRB_TT_OBJECT, mrb_class_get(mrb, "Binding"));
binding = mrb_obj_value(obj);
binding = mrb_binding_alloc(mrb);
proc = (struct RProc*)mrb_proc_get_caller(mrb, &env);
if (!env || MRB_PROC_CFUNC_P(proc)) {
proc = NULL;
env = NULL;
}
if (proc && !MRB_PROC_CFUNC_P(proc)) {
const mrb_irep *irep = proc->body.irep;
mrb_iv_set(mrb, binding, MRB_SYM(pc), mrb_fixnum_value(mrb->c->ci[-1].pc - irep->iseq - 1 /* step back */));
}
proc = mrb_binding_wrap_lvspace(mrb, proc, &env);
mrb_iv_set(mrb, binding, MRB_SYM(proc), mrb_obj_value(proc));
mrb_iv_set(mrb, binding, MRB_SYM(recv), self);
mrb_iv_set(mrb, binding, MRB_SYM(env), mrb_obj_value(env));
......@@ -42,7 +294,13 @@ mrb_mruby_binding_core_gem_init(mrb_state *mrb)
mrb_define_method(mrb, mrb->kernel_module, "binding", mrb_f_binding, MRB_ARGS_NONE());
mrb_define_method(mrb, binding, "local_variable_defined?", binding_local_variable_defined_p, MRB_ARGS_REQ(1));
mrb_define_method(mrb, binding, "local_variable_get", binding_local_variable_get, MRB_ARGS_REQ(1));
mrb_define_method(mrb, binding, "local_variable_set", binding_local_variable_set, MRB_ARGS_REQ(2));
mrb_define_method(mrb, binding, "local_variables", binding_local_variables, MRB_ARGS_NONE());
mrb_define_method(mrb, binding, "receiver", binding_receiver, MRB_ARGS_NONE());
mrb_define_method(mrb, binding, "source_location", binding_source_location, MRB_ARGS_NONE());
mrb_define_method(mrb, binding, "inspect", mrb_any_to_s, MRB_ARGS_NONE());
}
void
......
......@@ -9,3 +9,32 @@ assert("Binding#local_variables") do
end
assert_equal [:a, :b, :block], block.call(0).local_variables.sort
end
assert("Binding#local_variable_set") do
bind = binding
1.times {
assert_equal(9, bind.local_variable_set(:x, 9))
assert_raise(NameError) { x }
assert_equal([:bind, :x], bind.local_variables.sort)
}
end
assert("Binding#local_variable_get") do
bind = binding
x = 1
1.times {
y = 2
assert_equal(1, bind.local_variable_get(:x))
x = 10
assert_equal(10, bind.local_variable_get(:x))
assert_raise(NameError) { bind.local_variable_get(:y) }
assert_equal([:bind, :x], bind.local_variables.sort)
}
end
assert("Binding#source_location") do
skip unless -> {}.source_location
bind, source_location = binding, [__FILE__, __LINE__]
assert_equal source_location, bind.source_location
end
......@@ -5,4 +5,7 @@ MRuby::Gem::Specification.new('mruby-binding') do |spec|
spec.add_dependency('mruby-binding-core', :core => 'mruby-binding-core')
spec.add_dependency('mruby-eval', :core => 'mruby-eval')
spec.add_test_dependency('mruby-metaprog', :core => 'mruby-metaprog')
spec.add_test_dependency('mruby-method', :core => 'mruby-method')
spec.add_test_dependency('mruby-proc-ext', :core => 'mruby-proc-ext')
end
#include <mruby.h>
static mrb_value
binding_in_c(mrb_state *mrb, mrb_value self)
{
return mrb_funcall_argv(mrb, mrb_obj_value(mrb->object_class), mrb_intern_lit(mrb, "binding"), 0, NULL);
}
void
mrb_mruby_binding_gem_test(mrb_state *mrb)
{
mrb_define_method(mrb, mrb->object_class, "binding_in_c", binding_in_c, MRB_ARGS_NONE());
}
......@@ -2,4 +2,69 @@ assert("Binding#eval") do
b = nil
1.times { x, y, z = 1, 2, 3; [x,y,z]; b = binding }
assert_equal([1, 2, 3], b.eval("[x, y, z]"))
here = self
assert_equal(here, b.eval("self"))
end
assert("Binding#local_variables") do
block = Proc.new do |a|
b = 1
binding
end
bind = block.call(0)
assert_equal [:a, :b, :bind, :block], bind.local_variables.sort
bind.eval("x = 2")
assert_equal [:a, :b, :bind, :block, :x], bind.local_variables.sort
end
assert("Binding#local_variable_set") do
bind = binding
1.times {
assert_equal(9, bind.local_variable_set(:x, 9))
assert_equal(9, bind.eval("x"))
assert_equal([:bind, :x], bind.eval("local_variables.sort"))
}
end
assert("Binding#local_variable_get") do
bind = binding
x = 1
1.times {
y = 2
assert_equal(1, bind.local_variable_get(:x))
x = 10
assert_equal(10, bind.local_variable_get(:x))
assert_raise(NameError) { bind.local_variable_get(:y) }
bind.eval("z = 3")
assert_equal(3, bind.local_variable_get(:z))
bind.eval("y = 5")
assert_equal(5, bind.local_variable_get(:y))
assert_equal(2, y)
}
end
assert("Binding#source_location") do
skip unless -> {}.source_location
bind, source_location = binding, [__FILE__, __LINE__]
assert_equal source_location, bind.source_location
end
assert "Kernel#binding and .eval from C" do
bind = binding_in_c
assert_equal 5, bind.eval("2 + 3")
assert_nothing_raised { bind.eval("self") }
end
assert "Binding#eval with Binding.new via UnboundMethod" do
assert_raise(NoMethodError) { Class.instance_method(:new).bind_call(Binding) }
end
assert "Binding#eval with Binding.new via Method" do
# The following test is OK if SIGSEGV does not occur
cx = Class.new(Binding)
cx.define_singleton_method(:allocate, &Object.method(:allocate))
Class.instance_method(:new).bind_call(cx).eval("")
assert_true true
end
......@@ -9,10 +9,10 @@
#include <mruby/variable.h>
struct REnv *mrb_env_new(mrb_state *mrb, struct mrb_context *c, mrb_callinfo *ci, int nstacks, mrb_value *stack, struct RClass *tc);
mrb_value mrb_exec_irep(mrb_state *mrb, mrb_value self, struct RProc *p);
mrb_value mrb_exec_irep(mrb_state *mrb, mrb_value self, struct RProc *p, mrb_func_t posthook);
mrb_value mrb_obj_instance_eval(mrb_state *mrb, mrb_value self);
void mrb_codedump_all(mrb_state*, struct RProc*);
void mrb_proc_merge_lvar(mrb_state *mrb, mrb_irep *irep, struct REnv *env, int num, const mrb_sym *lv, const mrb_value *stack);
static struct RProc*
create_proc_from_string(mrb_state *mrb, const char *s, mrb_int len, mrb_value binding, const char *file, mrb_int line)
......@@ -127,7 +127,7 @@ create_proc_from_string(mrb_state *mrb, const char *s, mrb_int len, mrb_value bi
}
static mrb_value
exec_irep(mrb_state *mrb, mrb_value self, struct RProc *proc)
exec_irep(mrb_state *mrb, mrb_value self, struct RProc *proc, mrb_func_t posthook)
{
/* no argument passed from eval() */
mrb->c->ci->argc = 0;
......@@ -142,7 +142,38 @@ exec_irep(mrb_state *mrb, mrb_value self, struct RProc *proc)
}
/* clear block */
mrb->c->ci->stack[1] = mrb_nil_value();
return mrb_exec_irep(mrb, self, proc);
return mrb_exec_irep(mrb, self, proc, posthook);
}
static void
eval_merge_lvar(mrb_state *mrb, mrb_irep *irep, struct REnv *env, int num, const mrb_sym *lv, const mrb_value *stack)
{
mrb_assert(mrb->c->stend >= stack + num);
mrb_proc_merge_lvar(mrb, irep, env, num, lv, stack);
}
static mrb_value
eval_merge_lvar_hook(mrb_state *mrb, mrb_value dummy_self)
{
const mrb_callinfo *orig_ci = &mrb->c->ci[1];
const struct RProc *orig_proc = orig_ci->proc;
const mrb_irep *orig_irep = orig_proc->body.irep;
int orig_nlocals = orig_irep->nlocals;
if (orig_nlocals > 1) {
struct RProc *proc = (struct RProc *)orig_proc->upper;
struct REnv *env = MRB_PROC_ENV(orig_proc);
eval_merge_lvar(mrb, (mrb_irep *)proc->body.irep, env,
orig_nlocals - 1, orig_irep->lv,
mrb->c->ci->stack + 3 /* hook proc + exc + ret val */);
}
mrb_value exc = mrb->c->ci->stack[1];
if (!mrb_nil_p(exc)) {
mrb_exc_raise(mrb, exc);
}
return mrb->c->ci->stack[2];
}
static mrb_value
......@@ -154,15 +185,19 @@ f_eval(mrb_state *mrb, mrb_value self)
const char *file = NULL;
mrb_int line = 1;
struct RProc *proc;
mrb_func_t posthook = NULL;
mrb_get_args(mrb, "s|ozi", &s, &len, &binding, &file, &line);
proc = create_proc_from_string(mrb, s, len, binding, file, line);
if (!mrb_nil_p(binding)) {
self = mrb_iv_get(mrb, binding, MRB_SYM(recv));
if (mrb_env_p(mrb_iv_get(mrb, binding, MRB_SYM(env)))) {
posthook = eval_merge_lvar_hook;
}
}
mrb_assert(!MRB_PROC_CFUNC_P(proc));
return exec_irep(mrb, self, proc);
return exec_irep(mrb, self, proc, posthook);
}
static mrb_value
......@@ -187,7 +222,7 @@ f_instance_eval(mrb_state *mrb, mrb_value self)
MRB_PROC_SET_TARGET_CLASS(proc, mrb_class_ptr(cv));
mrb_assert(!MRB_PROC_CFUNC_P(proc));
mrb_vm_ci_target_class_set(mrb->c->ci, mrb_class_ptr(cv));
return exec_irep(mrb, self, proc);
return exec_irep(mrb, self, proc, NULL);
}
else {
mrb_get_args(mrb, "&", &b);
......
......@@ -21,6 +21,8 @@ typedef enum {
NOEX_RESPONDS = 0x80
} mrb_method_flag_t;
mrb_value mrb_proc_local_variables(mrb_state *mrb, const struct RProc *proc);
static mrb_value
mrb_f_nil(mrb_state *mrb, mrb_value cv)
{
......@@ -117,8 +119,6 @@ mrb_obj_ivar_set(mrb_state *mrb, mrb_value self)
return val;
}
mrb_value mrb_proc_local_variables(mrb_state *mrb, const struct RProc *proc);
/* 15.3.1.2.7 */
/* 15.3.1.3.28 */
/*
......
MRuby::Gem::Specification.new('mruby-proc-binding') do |spec|
spec.license = 'MIT'
spec.author = 'mruby developers'
spec.summary = 'Proc#binding method'
spec.add_dependency('mruby-binding-core', :core => 'mruby-binding-core')
spec.add_test_dependency('mruby-binding', :core => 'mruby-binding')
spec.add_test_dependency('mruby-compiler', :core => 'mruby-compiler')
end
#include <mruby.h>
#include <mruby/presym.h>
#include <mruby/proc.h>
#include <mruby/variable.h>
void mrb_proc_merge_lvar(mrb_state *mrb, mrb_irep *irep, struct REnv *env, int num, const mrb_sym *lv, const mrb_value *stack);
/* provided by mruby-proc-ext */
mrb_value mrb_proc_source_location(mrb_state *mrb, struct RProc *p);
/* provided by mruby-binding-core */
mrb_value mrb_binding_alloc(mrb_state *mrb);
struct RProc *mrb_binding_wrap_lvspace(mrb_state *mrb, const struct RProc *proc, struct REnv **envp);
static mrb_value
mrb_proc_binding(mrb_state *mrb, mrb_value procval)
{
mrb_value binding = mrb_binding_alloc(mrb);
const struct RProc *proc = mrb_proc_ptr(procval);
struct REnv *env;
mrb_value receiver;
if (!proc || MRB_PROC_CFUNC_P(proc) || !proc->upper || MRB_PROC_CFUNC_P(proc->upper)) {
env = NULL;
proc = NULL;
receiver = mrb_nil_value();
}
else {
env = MRB_PROC_ENV(proc);
mrb_assert(env);
proc = proc->upper;
receiver = MRB_ENV_LEN(env) > 0 ? env->stack[0] : mrb_nil_value();
}
proc = mrb_binding_wrap_lvspace(mrb, proc, &env);
mrb_iv_set(mrb, binding, MRB_SYM(proc), mrb_obj_value((void *)proc));
mrb_iv_set(mrb, binding, MRB_SYM(recv), receiver);
mrb_iv_set(mrb, binding, MRB_SYM(env), mrb_obj_value(env));
mrb_iv_set(mrb, binding, MRB_SYM(source_location), mrb_proc_source_location(mrb, mrb_proc_ptr(procval)));
return binding;
}
void
mrb_mruby_proc_binding_gem_init(mrb_state *mrb)
{
mrb_define_method(mrb, mrb->proc_class, "binding", mrb_proc_binding, MRB_ARGS_NONE());
}
void
mrb_mruby_proc_binding_gem_final(mrb_state *mrb)
{
}
#include <mruby.h>
#include <mruby/compile.h>
static mrb_value
proc_in_c(mrb_state *mrb, mrb_value self)
{
return mrb_load_string(mrb, "proc { |a, b| a + b }");
}
void
mrb_mruby_proc_binding_gem_test(mrb_state *mrb)
{
mrb_define_method(mrb, mrb->object_class, "proc_in_c", proc_in_c, MRB_ARGS_NONE());
}
assert "Proc#binding" do
block = ->(i) {}
a, b, c = 1, 2, 3
bind = block.binding
assert_equal([:a, :b, :bind, :block, :c], bind.local_variables.sort)
assert_equal(1, bind.local_variable_get(:a))
assert_equal(5, bind.eval("b + c"))
bind.local_variable_set(:x, 9)
assert_equal(9, bind.local_variable_get(:x))
end
assert("Binding#source_location after Proc#binding") do
skip unless -> {}.source_location
block, source_location = -> {}, [__FILE__, __LINE__]
assert_equal source_location, block.binding.source_location
end
assert "Proc#binding and .eval from C" do
bind = proc_in_c.binding
assert_nothing_raised { bind.eval("self") }
end
......@@ -13,11 +13,9 @@ mrb_proc_lambda_p(mrb_state *mrb, mrb_value self)
return mrb_bool_value(MRB_PROC_STRICT_P(p));
}
static mrb_value
mrb_proc_source_location(mrb_state *mrb, mrb_value self)
mrb_value
mrb_proc_source_location(mrb_state *mrb, struct RProc *p)
{
struct RProc *p = mrb_proc_ptr(self);
if (MRB_PROC_CFUNC_P(p)) {
return mrb_nil_value();
}
......@@ -34,6 +32,12 @@ mrb_proc_source_location(mrb_state *mrb, mrb_value self)
}
}
static mrb_value
mrb_proc_source_location_m(mrb_state *mrb, mrb_value self)
{
return mrb_proc_source_location(mrb, mrb_proc_ptr(self));
}
static mrb_value
mrb_proc_inspect(mrb_state *mrb, mrb_value self)
{
......@@ -183,7 +187,7 @@ mrb_mruby_proc_ext_gem_init(mrb_state* mrb)
{
struct RClass *p = mrb->proc_class;
mrb_define_method(mrb, p, "lambda?", mrb_proc_lambda_p, MRB_ARGS_NONE());
mrb_define_method(mrb, p, "source_location", mrb_proc_source_location, MRB_ARGS_NONE());
mrb_define_method(mrb, p, "source_location", mrb_proc_source_location_m, MRB_ARGS_NONE());
mrb_define_method(mrb, p, "to_s", mrb_proc_inspect, MRB_ARGS_NONE());
mrb_define_method(mrb, p, "inspect", mrb_proc_inspect, MRB_ARGS_NONE());
mrb_define_method(mrb, p, "parameters", mrb_proc_parameters, MRB_ARGS_NONE());
......
......@@ -372,6 +372,44 @@ mrb_proc_get_caller(mrb_state *mrb, struct REnv **envp)
return proc;
}
#define IREP_LVAR_MERGE_DEFAULT 50
#define IREP_LVAR_MERGE_MINIMUM 8
#define IREP_LVAR_MERGE_MAXIMUM 240
#ifdef MRB_IREP_LVAR_MERGE_LIMIT
# define IREP_LVAR_MERGE_LIMIT \
((MRB_IREP_LVAR_MERGE_LIMIT) < IREP_LVAR_MERGE_MINIMUM ? IREP_LVAR_MERGE_MINIMUM : \
(MRB_IREP_LVAR_MERGE_LIMIT) > IREP_LVAR_MERGE_MAXIMUM ? IREP_LVAR_MERGE_MAXIMUM : \
(MRB_IREP_LVAR_MERGE_LIMIT))
#else
# define IREP_LVAR_MERGE_LIMIT IREP_LVAR_MERGE_DEFAULT
#endif
void
mrb_proc_merge_lvar(mrb_state *mrb, mrb_irep *irep, struct REnv *env, int num, const mrb_sym *lv, const mrb_value *stack)
{
mrb_assert(!(irep->flags & MRB_IREP_NO_FREE));
if ((irep->nlocals + num) > IREP_LVAR_MERGE_LIMIT) {
mrb_raise(mrb, E_RUNTIME_ERROR, "too many local variables for binding (mruby limitation)");
}
if (!lv) {
mrb_raise(mrb, E_RUNTIME_ERROR, "unavailable local variable names");
}
irep->lv = (mrb_sym*)mrb_realloc(mrb, (mrb_sym*)irep->lv, sizeof(mrb_sym) * (irep->nlocals + num));
env->stack = (mrb_value*)mrb_realloc(mrb, env->stack, sizeof(mrb_value) * (irep->nlocals + 1 /* self */ + num));
mrb_sym *destlv = (mrb_sym*)irep->lv + irep->nlocals - 1 /* self */;
mrb_value *destst = env->stack + irep->nlocals;
memmove(destlv, lv, sizeof(mrb_sym) * num);
memmove(destst, stack, sizeof(mrb_value) * num);
irep->nlocals += num;
irep->nregs = irep->nlocals;
MRB_ENV_SET_LEN(env, irep->nlocals);
}
void
mrb_init_proc(mrb_state *mrb)
{
......
......@@ -483,13 +483,73 @@ mrb_funcall_argv(mrb_state *mrb, mrb_value self, mrb_sym mid, mrb_int argc, cons
return mrb_funcall_with_block(mrb, self, mid, argc, argv, mrb_nil_value());
}
#define DECOMPOSE32(n) (((n) >> 24) & 0xff), (((n) >> 16) & 0xff), (((n) >> 8) & 0xff), (((n) >> 0) & 0xff)
#define CATCH_HANDLER_MAKE_BYTECODE(t, b, e, j) t, DECOMPOSE32(b), DECOMPOSE32(e), DECOMPOSE32(j)
#define CATCH_HANDLER_NUM_TO_BYTE(n) ((n) * sizeof(struct mrb_irep_catch_handler))
static void
mrb_exec_irep_prepare_posthook(mrb_state *mrb, mrb_callinfo *ci, int nregs, mrb_func_t posthook)
{
/*
* stack: [proc, errinfo, return value by called proc]
*
* begin
* OP_NOP # A dummy instruction built in to make the catch handler react.
* ensure
* OP_EXCEPT R1 # Save the exception object.
* OP_CALL # Call a C function for the hook.
* # The stack is kept as it is in the called proc.
* # The exception will be rethrown within the hook function.
* end
*/
static const mrb_code hook_iseq[] = {
OP_NOP,
OP_EXCEPT, 1,
OP_CALL,
CATCH_HANDLER_MAKE_BYTECODE(MRB_CATCH_ENSURE, 0, 1, 1),
};
static const mrb_irep hook_irep = {
1, 3, 1, MRB_IREP_STATIC, hook_iseq,
NULL, NULL, NULL, NULL, NULL,
sizeof(hook_iseq) / sizeof(hook_iseq[0]) - CATCH_HANDLER_NUM_TO_BYTE(1),
0, 0, 0, 0
};
static const struct RProc hook_caller = {
NULL, NULL, MRB_TT_PROC, 7 /* GC_RED */, MRB_FL_OBJ_IS_FROZEN, { &hook_irep }, NULL, { NULL }
};
struct RProc *hook = mrb_proc_new_cfunc(mrb, posthook);
int acc = 2;
memmove(ci->stack + acc, ci->stack, sizeof(mrb_value) * nregs);
ci->stack[0] = mrb_obj_value(hook);
ci->stack[1] = mrb_nil_value();
mrb_callinfo hook_ci = { 0, 0, ci->acc, &hook_caller, ci->stack, &hook_iseq[1], { NULL } };
ci = cipush(mrb, acc, acc, NULL, ci[0].proc, ci[0].mid, ci[0].argc);
ci->u.env = ci[-1].u.env;
ci[-1] = hook_ci;
}
/*
* If `posthook` is given, `posthook` will be called even if an
* exception or global jump occurs in `p`. Exception or global jump objects
* are stored in `mrb->c->stack[1]` and should be rethrown in `posthook`.
*
* if (!mrb_nil_p(mrb->c->stack[1])) {
* mrb_exc_raise(mrb, mrb->c->stack[1]);
* }
*
* If you want to return the return value by `proc` as it is, please do
* `return mrb->c->stack[2]`.
*
* However, if `proc` is a C function, it will be ignored.
*/
mrb_value
mrb_exec_irep(mrb_state *mrb, mrb_value self, struct RProc *p)
mrb_exec_irep(mrb_state *mrb, mrb_value self, struct RProc *p, mrb_func_t posthook)
{
mrb_callinfo *ci = mrb->c->ci;
mrb_int keep, nregs;
mrb->c->ci->stack[0] = self;
ci->stack[0] = self;
mrb_vm_ci_proc_set(ci, p);
if (MRB_PROC_CFUNC_P(p)) {
return MRB_PROC_CFUNC(p)(mrb, self);
......@@ -497,12 +557,17 @@ mrb_exec_irep(mrb_state *mrb, mrb_value self, struct RProc *p)
nregs = p->body.irep->nregs;
if (ci->argc < 0) keep = 3;
else keep = ci->argc + 2;
int extra = posthook ? (2 /* hook proc + errinfo */) : 0;
if (nregs < keep) {
mrb_stack_extend(mrb, keep);
mrb_stack_extend(mrb, keep + extra);
}
else {
mrb_stack_extend(mrb, nregs);
stack_clear(mrb->c->ci->stack+keep, nregs-keep);
mrb_stack_extend(mrb, nregs + extra);
stack_clear(ci->stack+keep, nregs-keep + extra);
}
if (posthook) {
mrb_exec_irep_prepare_posthook(mrb, ci, (nregs < keep ? keep : nregs), posthook);
}
cipush(mrb, 0, 0, NULL, NULL, 0, 0);
......@@ -573,7 +638,7 @@ mrb_f_send(mrb_state *mrb, mrb_value self)
}
return MRB_METHOD_CFUNC(m)(mrb, self);
}
return mrb_exec_irep(mrb, self, MRB_METHOD_PROC(m));
return mrb_exec_irep(mrb, self, MRB_METHOD_PROC(m), NULL);
}
static mrb_value
......@@ -760,7 +825,7 @@ mrb_yield_cont(mrb_state *mrb, mrb_value b, mrb_value self, mrb_int argc, const
mrb->c->ci->stack[1] = mrb_ary_new_from_values(mrb, argc, argv);
mrb->c->ci->stack[2] = mrb_nil_value();
ci->argc = -1;
return mrb_exec_irep(mrb, self, p);
return mrb_exec_irep(mrb, self, p, NULL);
}
static struct RBreak*
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment