summaryrefslogtreecommitdiff
path: root/mrbgems/mruby-binding
diff options
context:
space:
mode:
Diffstat (limited to 'mrbgems/mruby-binding')
-rw-r--r--mrbgems/mruby-binding/README.md256
-rw-r--r--mrbgems/mruby-binding/src/binding.c119
-rw-r--r--mrbgems/mruby-binding/test/binding.c1
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)