diff options
Diffstat (limited to 'mrbgems/mruby-binding-core')
| -rw-r--r-- | mrbgems/mruby-binding-core/mrbgem.rake | 4 | ||||
| -rw-r--r-- | mrbgems/mruby-binding-core/src/binding-core.c | 266 | ||||
| -rw-r--r-- | mrbgems/mruby-binding-core/test/binding-core.rb | 29 |
3 files changed, 294 insertions, 5 deletions
diff --git a/mrbgems/mruby-binding-core/mrbgem.rake b/mrbgems/mruby-binding-core/mrbgem.rake index f6b34982f..c0ba48207 100644 --- a/mrbgems/mruby-binding-core/mrbgem.rake +++ b/mrbgems/mruby-binding-core/mrbgem.rake @@ -1,5 +1,7 @@ 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 diff --git a/mrbgems/mruby-binding-core/src/binding-core.c b/mrbgems/mruby-binding-core/src/binding-core.c index 0113fc030..8086185ea 100644 --- a/mrbgems/mruby-binding-core/src/binding-core.c +++ b/mrbgems/mruby-binding-core/src/binding-core.c @@ -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 diff --git a/mrbgems/mruby-binding-core/test/binding-core.rb b/mrbgems/mruby-binding-core/test/binding-core.rb index d93f43619..066e79b18 100644 --- a/mrbgems/mruby-binding-core/test/binding-core.rb +++ b/mrbgems/mruby-binding-core/test/binding-core.rb @@ -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 |
