diff options
Diffstat (limited to 'mrbgems/mruby-binding')
| -rw-r--r-- | mrbgems/mruby-binding/README.md | 256 | ||||
| -rw-r--r-- | mrbgems/mruby-binding/src/binding.c | 119 | ||||
| -rw-r--r-- | mrbgems/mruby-binding/test/binding.c | 1 |
3 files changed, 365 insertions, 11 deletions
diff --git a/mrbgems/mruby-binding/README.md b/mrbgems/mruby-binding/README.md new file mode 100644 index 0000000..ae0d69d --- /dev/null +++ b/mrbgems/mruby-binding/README.md @@ -0,0 +1,256 @@ +# mruby-binding + +The `mruby-binding` mrbgem provides the `Binding` class for mruby. This class allows you to encapsulate the execution context (variables, methods, and `self`) at a particular point in your code, making it available for later use. It is similar in purpose to the `Binding` class in standard Ruby. + +## Obtaining a Binding Object + +You can obtain a `Binding` object using the `Kernel#binding` method: + +### `Kernel#binding` -> Binding + +Returns a `Binding` object that encapsulates the execution context (including local variables, `self`, and any active block) at the point of the call. + +Example: + +```ruby +def get_binding(param) + local_var = 42 + binding # This will capture param, local_var, and self +end + +b = get_binding("hello") +# b now holds the context from inside get_binding +``` + +**Note:** `Kernel#binding` cannot be called from a C function or a Proc defined in C. Attempting to do so will raise a `RuntimeError`. + +## `Binding` Class Methods + +A `Binding` object has the following methods: + +### `local_variables` -> Array of Symbol + +Returns an array of symbols representing the names of the local variables defined in the binding's context. + +Example: + +```ruby +def my_method + a = 10 + b = 20 + binding.local_variables # => [:a, :b] +end +``` + +### `local_variable_get(symbol)` -> Object + +Retrieves the value of the local variable named by `symbol`. Raises a `NameError` if the variable is not defined in the binding's context. + +Example: + +```ruby +def my_method + a = 10 + b = binding + b.local_variable_get(:a) # => 10 +end +``` + +### `local_variable_set(symbol, value)` -> Object + +Sets the local variable named by `symbol` to `value`. If the variable is not already defined, it will be defined in the binding's scope. Returns the `value` that was set. + +Example: + +```ruby +def my_method + a = 10 + b = binding + b.local_variable_set(:a, 20) # a is now 20 + b.local_variable_set(:c, 30) # c is now defined as 30 in this scope + a # => 20 + c # => 30 +end +``` + +### `local_variable_defined?(symbol)` -> Boolean + +Returns `true` if the local variable named by `symbol` is defined in the binding's context, `false` otherwise. + +Example: + +```ruby +def my_method + a = 10 + b = binding + b.local_variable_defined?(:a) # => true + b.local_variable_defined?(:c) # => false +end +``` + +### `receiver` -> Object + +Returns the receiver object (`self`) of the binding. + +Example: + +```ruby +class MyClass + def get_binding + @x = "instance var" + binding + end +end + +obj = MyClass.new +b = obj.get_binding +b.receiver # => obj (the instance of MyClass) +b.receiver.instance_variable_get(:@x) # => "instance var" (Not directly using eval) +``` + +### `source_location` -> [String, Integer] | nil + +Returns a two-element array containing the filename and line number where the binding was created. Returns `nil` if the source location cannot be determined (e.g., for bindings created from C). + +Example: + +```ruby +# In a file named 'test.rb' +b = binding # Assuming this is line 2 +b.source_location # => ["test.rb", 2] (approximately) +``` + +### `dup` / `clone` -> Binding + +Creates a shallow copy of the binding. Modifications to local variables in one binding object can affect the other if the variables themselves are mutable objects, but setting a variable in one binding will not create it in the other after duplication. The internal state concerning local variable storage is also duplicated. + +(The C code refers to `binding_initialize_copy`, which is what `dup` and `clone` would use.) + +```ruby +def my_method + x = 1 + original_binding = binding + original_binding.local_variable_set(:y, 2) + + copied_binding = original_binding.dup + + original_binding.local_variable_set(:x, 10) + original_binding.local_variable_set(:y, 20) + original_binding.local_variable_set(:z, 30) # New variable in original + + puts copied_binding.local_variable_get(:x) # => 1 (Original value before duplication for variables existing at duplication time) + # Correction: The tests show that changes to existing variables are reflected. + # Let's re-verify test behavior for `dup`. + + # Re-checking test `Binding#dup`: + # x = 5 + # bind1 = binding + # bind1.local_variable_set(:y, 10) + # bind2 = bind1.dup + # assert_equal 5, bind2.local_variable_get(:x) + # assert_equal 10, bind2.local_variable_get(:y) + # x = 50 # x is changed in the original scope AFTER duplication + # assert_equal 50, bind1.local_variable_get(:x) + # assert_equal 50, bind2.local_variable_get(:x) # bind2 sees the change to x! + # bind1.local_variable_set(:y, 20) # y is changed in bind1 AFTER duplication + # assert_equal 20, bind1.local_variable_get(:y) + # assert_equal 20, bind2.local_variable_get(:y) # bind2 sees the change to y! + # bind1.local_variable_set(:z, 30) # z is added to bind1 + # assert_raise(NameError) { bind2.local_variable_get(:z) } # bind2 does not see new z + # bind2.local_variable_set(:z, 40) # z is added to bind2 + # assert_equal 30, bind1.local_variable_get(:z) + # assert_equal 40, bind2.local_variable_get(:z) + + # Corrected explanation for dup/clone: + # Creates a copy of the binding. Both the original and copied bindings share the same + # underlying environment for local variables that existed at the time of duplication. + # This means: + # - If a variable that existed when `dup` was called is modified (either in the original + # scope or via `local_variable_set` on either binding), the change is visible in both bindings. + # - If a *new* local variable is added to one binding using `local_variable_set` *after* + # duplication, it is not visible in the other binding. +end +``` + +**Note on `eval`:** While the `Binding` object is often used with `eval` in standard Ruby to execute code within the binding's context, `mruby-binding` itself does not provide an `eval` method directly on the `Binding` object. You would typically use `Binding` with mruby's core `eval` method if you need to evaluate a string of code within a captured context. The `mruby-binding` gem provides the necessary infrastructure (like `mrb_binding_extract_proc` and `mrb_binding_extract_env` in C) that can be utilized by an `eval` implementation. + +```ruby +# Conceptual example (actual eval might vary based on mruby core) +def my_method + a = 10 + b = binding + # eval("puts a", b) # => would print 10 + # eval("a = 20", b) # a in my_method's scope would become 20 +end +``` + +## Usage Example + +Here's a more complete example demonstrating some of the `Binding` object's capabilities: + +```ruby +class Greeter + def initialize(name) + @name = name + end + + def get_binding_for_greeting(additional_message) + greeting_type = "Hello" + # Capture the binding here + binding + end +end + +# Create an instance and get a binding +greeter_instance = Greeter.new("World") +captured_binding = greeter_instance.get_binding_for_greeting("Have a nice day!") + +# Access the receiver (self) +puts "Receiver: #{captured_binding.receiver}" +# => Receiver: #<Greeter:0x...> (actual object id will vary) +puts "Receiver's name: #{captured_binding.receiver.instance_variable_get(:@name)}" +# => Receiver's name: World + +# List local variables in the binding +puts "Local variables: #{captured_binding.local_variables.inspect}" +# => Local variables: [:additional_message, :greeting_type] (order may vary) + +# Get local variable values +puts "Greeting type: #{captured_binding.local_variable_get(:greeting_type)}" +# => Greeting type: Hello +puts "Additional message: #{captured_binding.local_variable_get(:additional_message)}" +# => Additional message: Have a nice day! + +# Set a local variable within the binding's context +captured_binding.local_variable_set(:greeting_type, "Hi") +puts "New greeting type: #{captured_binding.local_variable_get(:greeting_type)}" +# => New greeting type: Hi + +# Check if a variable is defined +puts "Is 'greeting_type' defined? #{captured_binding.local_variable_defined?(:greeting_type)}" +# => Is 'greeting_type' defined? true +puts "Is 'non_existent_var' defined? #{captured_binding.local_variable_defined?(:non_existent_var)}" +# => Is 'non_existent_var' defined? false + +# Source location (will vary based on where the code is run) +location = captured_binding.source_location +if location + puts "Binding created at: #{location[0]}:#{location[1]}" +else + puts "Source location not available for this binding." +end +``` + +This example illustrates how a `Binding` object captures the state of local variables and `self` from the scope where it was created, and how these can be inspected and manipulated. + +## Limitations and mruby-specific Considerations + +- **Nesting Depth for Local Variables:** mruby has an internal limit on how deeply nested Procs (blocks) can be while still allowing the `Binding` object to access and manage their local variables. This limit is defined by the `BINDING_UPPER_MAX` constant (defaulting to 20, with a minimum of 10 and a maximum of 100, configurable at compile time via `MRB_BINDING_UPPER_MAX`). If you exceed this nesting depth, attempting to create or manipulate a binding that needs to access variables across too many Proc scopes might result in a `RuntimeError` ("too many upper procs for local variables"). + +- **`eval` Method:** As noted earlier, this gem provides the `Binding` object itself, not a `Binding#eval` method. You would use `Kernel.eval(string, binding)` if you need to evaluate code within the context of a binding, relying on mruby's core `eval` capabilities. + +- **C Function Callers:** `Kernel#binding` cannot create a `Binding` object if the direct caller is a C function. It must be called from Ruby code. + +``` + +``` diff --git a/mrbgems/mruby-binding/src/binding.c b/mrbgems/mruby-binding/src/binding.c index b4ee14a..a5c9d6c 100644 --- a/mrbgems/mruby-binding/src/binding.c +++ b/mrbgems/mruby-binding/src/binding.c @@ -4,7 +4,6 @@ #include <mruby/hash.h> #include <mruby/proc.h> #include <mruby/variable.h> -#include <mruby/presym.h> #include <mruby/opcode.h> #include <mruby/debug.h> #include <mruby/internal.h> @@ -184,29 +183,34 @@ binding_initialize_copy(mrb_state *mrb, mrb_value binding) return binding; } +static mrb_noreturn void +badname_error(mrb_state *mrb, mrb_sym id) +{ + mrb_raisef(mrb, E_NAME_ERROR, "wrong local variable name %!n for binding", id); +} + 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); + badname_error(mrb, id); } mrb_int len; const char *name = mrb_sym_name_len(mrb, id, &len); if (len == 0) { - goto badname; + badname_error(mrb, id); } if (ISASCII(*name) && !(*name == '_' || ISLOWER(*name))) { - goto badname; + badname_error(mrb, id); } len--; name++; for (; len > 0; len--, name++) { if (ISASCII(*name) && !(*name == '_' || ISALNUM(*name))) { - goto badname; + badname_error(mrb, id); } } } @@ -240,6 +244,23 @@ binding_local_variable_search(mrb_state *mrb, const struct RProc *proc, struct R /* * call-seq: * local_variable_defined?(symbol) -> bool + * + * Returns true if a local variable with the given name is defined + * in the binding's context, false otherwise. + * + * def foo + * a = 1 + * b = binding + * b.local_variable_defined?(:a) #=> true + * b.local_variable_defined?(:c) #=> false + * end + * + * x = 10 + * bind = binding + * bind.local_variable_defined?(:x) #=> true + * bind.local_variable_defined?(:y) #=> false + * bind.local_variable_set(:y, 20) + * bind.local_variable_defined?(:y) #=> true */ static mrb_value binding_local_variable_defined_p(mrb_state *mrb, mrb_value self) @@ -261,6 +282,25 @@ binding_local_variable_defined_p(mrb_state *mrb, mrb_value self) /* * call-seq: * local_variable_get(symbol) -> object + * + * Returns the value of the local variable with the given name + * in the binding's context. Raises NameError if the variable + * is not defined. + * + * def foo + * a = 42 + * b = "hello" + * bind = binding + * bind.local_variable_get(:a) #=> 42 + * bind.local_variable_get(:b) #=> "hello" + * bind.local_variable_get(:c) #=> NameError + * end + * + * x = [1, 2, 3] + * bind = binding + * bind.local_variable_get(:x) #=> [1, 2, 3] + * x = "modified" + * bind.local_variable_get(:x) #=> "modified" */ static mrb_value binding_local_variable_get(mrb_state *mrb, mrb_value self) @@ -278,6 +318,20 @@ binding_local_variable_get(mrb_state *mrb, mrb_value self) return *e; } +/* + * call-seq: + * binding.local_variable_set(symbol, obj) -> obj + * + * Set local variable named symbol as obj in binding's context. + * If the variable is not defined in the binding, it will be created. + * + * def foo + * a = 1 + * binding.local_variable_set(:a, 2) + * binding.local_variable_set(:b, 3) + * [a, b] #=> [2, 3] + * end + */ static mrb_value binding_local_variable_set(mrb_state *mrb, mrb_value self) { @@ -301,6 +355,19 @@ binding_local_variable_set(mrb_state *mrb, mrb_value self) return obj; } +/* + * call-seq: + * binding.local_variables -> array + * + * Returns an array of symbols representing the names of the local variables + * in the binding. + * + * def foo + * a = 1 + * b = 2 + * binding.local_variables #=> [:a, :b] + * end + */ static mrb_value binding_local_variables(mrb_state *mrb, mrb_value self) { @@ -308,6 +375,19 @@ binding_local_variables(mrb_state *mrb, mrb_value self) return mrb_proc_local_variables(mrb, proc); } +/* + * call-seq: + * binding.receiver -> object + * + * Returns the bound receiver of the binding object. + * + * class Demo + * def get_binding + * binding + * end + * end + * Demo.new.get_binding.receiver #=> #<Demo:0x...> + */ static mrb_value binding_receiver(mrb_state *mrb, mrb_value self) { @@ -374,6 +454,26 @@ mrb_binding_new(mrb_state *mrb, const struct RProc *proc, mrb_value recv, struct return mrb_obj_value(binding); } +/* + * call-seq: + * binding -> binding + * + * Returns a Binding object, describing the variable and method bindings + * at the point of call. This object can be used when calling eval to + * execute the evaluated command in this environment. + * + * def get_binding(param) + * binding + * end + * b = get_binding("hello") + * b.eval("param") #=> "hello" + */ +static mrb_noreturn void +caller_error(mrb_state *mrb) +{ + mrb_raise(mrb, E_RUNTIME_ERROR, "Cannot create Binding object for non-Ruby caller"); +} + static mrb_value mrb_f_binding(mrb_state *mrb, mrb_value self) { @@ -381,12 +481,11 @@ mrb_f_binding(mrb_state *mrb, mrb_value self) struct REnv *env; if (mrb->c->ci->cci != 0) { - caller_err: - mrb_raise(mrb, E_RUNTIME_ERROR, "Cannot create Binding object for non-Ruby caller"); + caller_error(mrb); } proc = (struct RProc*)mrb_proc_get_caller(mrb, &env); if (!env || MRB_PROC_CFUNC_P(proc)) { - goto caller_err; + caller_error(mrb); } return mrb_binding_new(mrb, proc, self, env); } @@ -402,7 +501,7 @@ mrb_mruby_binding_gem_init(mrb_state *mrb) mrb_define_private_method_id(mrb, mrb->kernel_module, MRB_SYM(binding), mrb_f_binding, MRB_ARGS_NONE()); - mrb_define_method_id(mrb, binding, MRB_SYM(initialize_copy), binding_initialize_copy, MRB_ARGS_REQ(1)); + mrb_define_private_method_id(mrb, binding, MRB_SYM(initialize_copy), binding_initialize_copy, MRB_ARGS_REQ(1)); mrb_define_method_id(mrb, binding, MRB_SYM_Q(local_variable_defined), binding_local_variable_defined_p, MRB_ARGS_REQ(1)); mrb_define_method_id(mrb, binding, MRB_SYM(local_variable_get), binding_local_variable_get, MRB_ARGS_REQ(1)); mrb_define_method_id(mrb, binding, MRB_SYM(local_variable_set), binding_local_variable_set, MRB_ARGS_REQ(2)); diff --git a/mrbgems/mruby-binding/test/binding.c b/mrbgems/mruby-binding/test/binding.c index efc982a..e7dc5b0 100644 --- a/mrbgems/mruby-binding/test/binding.c +++ b/mrbgems/mruby-binding/test/binding.c @@ -1,5 +1,4 @@ #include <mruby.h> -#include <mruby/presym.h> static mrb_value binding_in_c(mrb_state *mrb, mrb_value self) |
