diff options
| author | dearblue <[email protected]> | 2021-02-22 23:32:43 +0900 |
|---|---|---|
| committer | dearblue <[email protected]> | 2021-02-22 23:32:43 +0900 |
| commit | 927615e1f072d8fff3d9b84660cdce15a239e36c (patch) | |
| tree | 14e14aa860b778176435be8d6d666917d891a9d8 /mrbgems/mruby-binding-core | |
| parent | 792f6ac6700469ddf9be8f87ca3376082f9af7f3 (diff) | |
| download | mruby-927615e1f072d8fff3d9b84660cdce15a239e36c.tar.gz mruby-927615e1f072d8fff3d9b84660cdce15a239e36c.zip | |
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.
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 |
