Skip to content
This repository was archived by the owner on Dec 4, 2023. It is now read-only.

Commit 75823c1

Browse files
committed
Merge pull request #364 from cowboyd/4.5/ruby-maybe-values
Maybe APIs
2 parents d5e26af + cbfb1b1 commit 75823c1

15 files changed

Lines changed: 237 additions & 24 deletions

File tree

ext/v8/bool.h

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,14 @@ namespace rr {
2323
*/
2424
class Bool : public Equiv {
2525
public:
26+
/**
27+
* Use to convert methods that return Maybe<bool> to a Ruby
28+
* VALUE, Such as `Maybe<bool> v8::Object::Get()`:
29+
*
30+
* return Bool::Maybe(object->Get(context, key));
31+
*/
32+
typedef Equiv::Maybe<bool, Bool> Maybe;
33+
2634
/**
2735
* Construct a Bool from a Ruby VALUE
2836
*/

ext/v8/equiv.h

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -40,6 +40,27 @@ namespace rr {
4040
*/
4141
inline operator VALUE() { return value; }
4242

43+
44+
/**
45+
* A Maybe class for primitive conversions. Specify the primitive
46+
* type and the covversion type, and it will be wrapped in a
47+
* Maybe. E.g.
48+
*
49+
* v8::Maybe<bool> maybe = methodReturningMaybeBool();
50+
* return Equiv::Maybe<bool, Bool>(maybe);
51+
*
52+
* will yield a V8::C::Maybe wrapping the underlying maybe value.
53+
*/
54+
template <class T, class S>
55+
class Maybe : public rr::Maybe {
56+
public:
57+
Maybe(v8::Maybe<T> maybe) {
58+
if (maybe.IsJust()) {
59+
just(S(maybe.FromJust()));
60+
}
61+
}
62+
};
63+
4364
protected:
4465
VALUE value;
4566
};

ext/v8/init.cc

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@ extern "C" {
1313
Handles::Init();
1414
Context::Init();
1515
Backref::Init();
16+
Maybe::Init();
1617
Value::Init();
1718
Object::Init();
1819
Primitive::Init();

ext/v8/maybe.cc

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
#include "rr.h"
2+
3+
namespace rr {
4+
VALUE Maybe::JustClass;
5+
VALUE Maybe::Nothing;
6+
}

ext/v8/maybe.h

Lines changed: 71 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,71 @@
1+
// -*- mode: c++ -*-
2+
#ifndef RR_MAYBE_H
3+
#define RR_MAYBE_H
4+
5+
namespace rr {
6+
7+
/**
8+
* A base class for conversion objects that will return an instance
9+
* of `V8::C::Maybe` depending on whether a value is available.
10+
*
11+
* Sprinkled throughout the V8 API, there are methods that may or
12+
* may not return a value based on whether there is an Exception
13+
* that is pending in the context of that operation.
14+
*
15+
* This class provides the basis for helpers to convert the C++
16+
* Maybe<T> and MaybeLocal<T> objects into Ruby objects that force
17+
* you to check if an operation suceeded.
18+
*
19+
* By default, it will be `V8::C::Nothing`, but if, in the
20+
* constructor, you call `just(VALUE value)`, then it will take on
21+
* that value.
22+
*/
23+
class Maybe {
24+
public:
25+
26+
/**
27+
* V8::C::Just
28+
*/
29+
static VALUE JustClass;
30+
31+
/**
32+
* Singleton instance of `V8::C::Nothing`
33+
*/
34+
static VALUE Nothing;
35+
36+
/**
37+
* By default, everything starts out as nothing.
38+
*/
39+
Maybe() : object(Nothing) {}
40+
41+
/**
42+
* Convert this seemlessly to a VALUE, which in this case is just
43+
* the v8::C::Maybe ruby instance.
44+
*/
45+
inline operator VALUE() {
46+
return object;
47+
}
48+
49+
static inline void Init() {
50+
rb_eval_string("require 'v8/c/maybe'");
51+
JustClass = rb_eval_string("::V8::C::Maybe::Just");
52+
Nothing = rb_eval_string("::V8::C::Maybe.nothing");
53+
}
54+
55+
protected:
56+
57+
/**
58+
* Subclasses call this method in the constructor in order to
59+
* indicate that this is a `V8::C::Just`, and to pass the
60+
* underlying value.
61+
*/
62+
inline void just(VALUE v) {
63+
object = rb_funcall(JustClass, rb_intern("new"), 1, v);
64+
}
65+
66+
// the underlying value.
67+
VALUE object;
68+
};
69+
}
70+
71+
#endif /* RR_MAYBE_H */

ext/v8/object.cc

Lines changed: 12 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -20,25 +20,29 @@ namespace rr {
2020
}
2121

2222
// TODO: Allow setting of property attributes
23-
VALUE Object::Set(VALUE self, VALUE key, VALUE value) {
23+
VALUE Object::Set(VALUE self, VALUE r_context, VALUE key, VALUE value) {
2424
Object object(self);
25-
Locker lock(object.getIsolate());
25+
Context context(r_context);
26+
Isolate isolate(object.getIsolate());
27+
Locker lock(isolate);
2628

2729
if (rb_obj_is_kind_of(key, rb_cNumeric)) {
28-
return Bool(object->Set(UInt32(key), Value::rubyObjectToHandle(object.getIsolate(), value)));
30+
return Bool::Maybe(object->Set(context, UInt32(key), Value::rubyObjectToHandle(isolate, value)));
2931
} else {
30-
return Bool(object->Set(*Value(key), Value::rubyObjectToHandle(object.getIsolate(), value)));
32+
return Bool::Maybe(object->Set(context, *Value(key), Value::rubyObjectToHandle(isolate, value)));
3133
}
3234
}
3335

34-
VALUE Object::Get(VALUE self, VALUE key) {
36+
VALUE Object::Get(VALUE self, VALUE r_context, VALUE key) {
3537
Object object(self);
36-
Locker lock(object.getIsolate());
38+
Context context(r_context);
39+
Isolate isolate(object.getIsolate());
40+
Locker lock(isolate);
3741

3842
if (rb_obj_is_kind_of(key, rb_cNumeric)) {
39-
return Value::handleToRubyObject(object.getIsolate(), object->Get(UInt32(key)));
43+
return Value::Maybe(isolate, object->Get(context, UInt32(key)));
4044
} else {
41-
return Value::handleToRubyObject(object.getIsolate(), object->Get(*Value(key)));
45+
return Value::Maybe(isolate, object->Get(context, *Value(key)));
4246
}
4347
}
4448

ext/v8/object.h

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -7,9 +7,9 @@ namespace rr {
77
public:
88
static void Init();
99
static VALUE New(VALUE self, VALUE isolate);
10-
static VALUE Set(VALUE self, VALUE key, VALUE value);
10+
static VALUE Set(VALUE self, VALUE context, VALUE key, VALUE value);
1111
// static VALUE ForceSet(VALUE self, VALUE key, VALUE value);
12-
static VALUE Get(VALUE self, VALUE key);
12+
static VALUE Get(VALUE self, VALUE context, VALUE key);
1313
// static VALUE GetPropertyAttributes(VALUE self, VALUE key);
1414
// static VALUE Has(VALUE self, VALUE key);
1515
// static VALUE Delete(VALUE self, VALUE key);

ext/v8/rr.h

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,7 @@ inline VALUE not_implemented(const char* message) {
1717
}
1818

1919
#include "class_builder.h"
20-
20+
#include "maybe.h"
2121
#include "equiv.h"
2222
#include "bool.h"
2323
#include "pointer.h"

ext/v8/value.h

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,28 @@
1+
// -*- mode: c++ -*-
12
#ifndef RR_VALUE
23
#define RR_VALUE
34

45
namespace rr {
56

67
class Value : public Ref<v8::Value> {
78
public:
9+
/**
10+
* A conversion class for down-thunking a MaybeLocal<v8::Value>
11+
* and returning it to Ruby as a `V8::C::Maybe`. If there is a
12+
* value present, then run it through the value conversion to get
13+
* the most specific subclass of Value:
14+
*
15+
* return Value::Maybe(object->Get(cxt, key));
16+
*/
17+
class Maybe : public rr::Maybe {
18+
public:
19+
Maybe(v8::Isolate* isolate, v8::MaybeLocal<v8::Value> maybe) {
20+
if (!maybe.IsEmpty()) {
21+
just(Value::handleToRubyObject(isolate, maybe.ToLocalChecked()));
22+
}
23+
}
24+
};
25+
826
static void Init();
927

1028
static VALUE IsUndefined(VALUE self);

lib/v8/c/maybe.rb

Lines changed: 78 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,78 @@
1+
module V8
2+
module C
3+
##
4+
# Some V8 API methods return a `Maybe` value that force you to check
5+
# if the operation failed or not before accessing the underlying
6+
# value. This implements the `v8::Maybe` and `v8::MaybeLocal` apis,
7+
# and is returned by those low-level C methods:
8+
#
9+
# maybe = object.Get(context, key)
10+
# if maybe.IsJust()
11+
# puts maybe.class #=> V8::C::Maybe::Just
12+
# puts "the value is #{maybe.FromJust()}"
13+
# else
14+
# puts maybe.class #=> V8::C::Maybe::Nothing
15+
# puts "operation failed!"
16+
# end
17+
#
18+
# Note: Instances of `V8::C::Maybe` are only ever created by the C
19+
# extension, and should not ever be instantiated from Ruby code.
20+
class Maybe
21+
22+
##
23+
# If true, then this is an instance of `V8::C::Just`, and it does
24+
# wrap a value
25+
def IsJust()
26+
false
27+
end
28+
29+
##
30+
# If true, then this is an instance of `V8::C::Nothing`, and it
31+
# does not contain a value. Any attempt to access the value will
32+
# raise an error.
33+
def IsNothing()
34+
false
35+
end
36+
37+
##
38+
# A Maybe that *does* contain a value.
39+
class Just < Maybe
40+
def initialize(value)
41+
@value = value
42+
end
43+
44+
def IsJust()
45+
true
46+
end
47+
48+
def FromJust()
49+
@value
50+
end
51+
end
52+
53+
##
54+
# A Maybe that *does* not contain any value
55+
class Nothing < Maybe
56+
57+
def IsNothing()
58+
true
59+
end
60+
61+
def FromJust()
62+
fail PendingExceptionError, "no value was available because an exception is pending in this context"
63+
end
64+
end
65+
66+
##
67+
# Singleton instance of Nothing.
68+
#
69+
# We only need one instance of Nothing because there is no
70+
# additional state apart from its nothingness.
71+
def self.nothing
72+
@nothing ||= Nothing.new
73+
end
74+
end
75+
76+
class PendingExceptionError < StandardError;end
77+
end
78+
end

0 commit comments

Comments
 (0)