18 |
18 |
#define IS_DLEXT(e) (strcmp((e), DLEXT) == 0)
|
19 |
19 |
#endif
|
20 |
20 |
|
21 |
|
|
22 |
21 |
static const char *const loadable_ext[] = {
|
23 |
22 |
".rb", DLEXT,
|
24 |
23 |
#ifdef DLEXT2
|
... | ... | |
63 |
62 |
return GET_VM()->loaded_features;
|
64 |
63 |
}
|
65 |
64 |
|
|
65 |
static void
|
|
66 |
reset_loaded_features_snapshot(void)
|
|
67 |
{
|
|
68 |
rb_vm_t *vm = GET_VM();
|
|
69 |
rb_ary_replace(vm->loaded_features_snapshot, vm->loaded_features);
|
|
70 |
}
|
|
71 |
|
|
72 |
static VALUE
|
|
73 |
get_loaded_features_index_raw(void)
|
|
74 |
{
|
|
75 |
return GET_VM()->loaded_features_index;
|
|
76 |
}
|
|
77 |
|
66 |
78 |
static st_table *
|
67 |
79 |
get_loading_table(void)
|
68 |
80 |
{
|
69 |
81 |
return GET_VM()->loading_table;
|
70 |
82 |
}
|
71 |
83 |
|
|
84 |
static void
|
|
85 |
features_index_add_single(VALUE short_feature, VALUE offset)
|
|
86 |
{
|
|
87 |
VALUE features_index, this_feature_index;
|
|
88 |
features_index = get_loaded_features_index_raw();
|
|
89 |
if ((this_feature_index = rb_hash_lookup(features_index, short_feature)) == Qnil) {
|
|
90 |
this_feature_index = rb_ary_new();
|
|
91 |
rb_hash_aset(features_index, short_feature, this_feature_index);
|
|
92 |
}
|
|
93 |
rb_ary_push(this_feature_index, offset);
|
|
94 |
}
|
|
95 |
|
|
96 |
/* Add to the loaded-features index all the required entries for
|
|
97 |
`feature`, located at `offset` in $LOADED_FEATURES. We add an
|
|
98 |
index entry at each string `short_feature` for which
|
|
99 |
feature == "#{prefix}#{short_feature}#{e}"
|
|
100 |
where `e` is empty or matches %r{^\.[^./]*$}, and `prefix` is empty
|
|
101 |
or ends in '/'. This maintains the invariant that `rb_feature_p()`
|
|
102 |
relies on for its fast lookup.
|
|
103 |
*/
|
|
104 |
static void
|
|
105 |
features_index_add(VALUE feature, VALUE offset)
|
|
106 |
{
|
|
107 |
VALUE short_feature;
|
|
108 |
const char *feature_str, *feature_end, *ext, *p;
|
|
109 |
|
|
110 |
feature_str = StringValuePtr(feature);
|
|
111 |
feature_end = feature_str + RSTRING_LEN(feature);
|
|
112 |
|
|
113 |
for (ext = feature_end; ext > feature_str; ext--)
|
|
114 |
if (*ext == '.' || *ext == '/')
|
|
115 |
break;
|
|
116 |
if (*ext != '.')
|
|
117 |
ext = NULL;
|
|
118 |
/* Now `ext` points to the only string matching %r{^\.[^./]*$} that is
|
|
119 |
at the end of `feature`, or is NULL if there is no such string. */
|
|
120 |
|
|
121 |
p = ext ? ext : feature_end;
|
|
122 |
while (1) {
|
|
123 |
p--;
|
|
124 |
while (p >= feature_str && *p != '/')
|
|
125 |
p--;
|
|
126 |
if (p < feature_str)
|
|
127 |
break;
|
|
128 |
/* Now *p == '/'. We reach this point for every '/' in `feature`. */
|
|
129 |
short_feature = rb_str_substr(feature, p + 1 - feature_str, feature_end - p - 1);
|
|
130 |
features_index_add_single(short_feature, offset);
|
|
131 |
if (ext) {
|
|
132 |
short_feature = rb_str_substr(feature, p + 1 - feature_str, ext - p - 1);
|
|
133 |
features_index_add_single(short_feature, offset);
|
|
134 |
}
|
|
135 |
}
|
|
136 |
features_index_add_single(feature, offset);
|
|
137 |
if (ext) {
|
|
138 |
short_feature = rb_str_substr(feature, 0, ext - feature_str);
|
|
139 |
features_index_add_single(short_feature, offset);
|
|
140 |
}
|
|
141 |
}
|
|
142 |
|
|
143 |
static VALUE
|
|
144 |
get_loaded_features_index(void)
|
|
145 |
{
|
|
146 |
VALUE features;
|
|
147 |
int i;
|
|
148 |
rb_vm_t *vm = GET_VM();
|
|
149 |
|
|
150 |
if (!rb_ary_shared_with_p(vm->loaded_features_snapshot, vm->loaded_features)) {
|
|
151 |
/* The sharing was broken; something (other than us in rb_provide_feature())
|
|
152 |
modified loaded_features. Rebuild the index. */
|
|
153 |
rb_hash_clear(vm->loaded_features_index);
|
|
154 |
features = vm->loaded_features;
|
|
155 |
for (i = 0; i < RARRAY_LEN(features); i++) {
|
|
156 |
VALUE entry, as_str;
|
|
157 |
as_str = entry = rb_ary_entry(features, i);
|
|
158 |
StringValue(as_str);
|
|
159 |
if (as_str != entry)
|
|
160 |
rb_ary_store(features, i, as_str);
|
|
161 |
rb_str_freeze(as_str);
|
|
162 |
features_index_add(as_str, INT2FIX(i));
|
|
163 |
}
|
|
164 |
reset_loaded_features_snapshot();
|
|
165 |
}
|
|
166 |
return vm->loaded_features_index;
|
|
167 |
}
|
|
168 |
|
72 |
169 |
/* This searches `load_path` for a value such that
|
73 |
170 |
name == "#{load_path[i]}/#{feature}"
|
74 |
171 |
if `feature` is a suffix of `name`, or otherwise
|
... | ... | |
142 |
239 |
static int
|
143 |
240 |
rb_feature_p(const char *feature, const char *ext, int rb, int expanded, const char **fn)
|
144 |
241 |
{
|
145 |
|
VALUE v, features, p, load_path = 0;
|
|
242 |
VALUE features, features_index, feature_val, this_feature_index, v, p, load_path = 0;
|
146 |
243 |
const char *f, *e;
|
147 |
244 |
long i, len, elen, n;
|
148 |
245 |
st_table *loading_tbl;
|
... | ... | |
161 |
258 |
type = 0;
|
162 |
259 |
}
|
163 |
260 |
features = get_loaded_features();
|
164 |
|
for (i = 0; i < RARRAY_LEN(features); ++i) {
|
165 |
|
/* This loop searches `features` for an entry such that either
|
166 |
|
"#{features[i]}" == "#{load_path[j]}/#{feature}#{e}"
|
167 |
|
for some j, or
|
168 |
|
"#{features[i]}" == "#{feature}#{e}"
|
169 |
|
Here `e` is an "allowed" extension -- either empty or one
|
170 |
|
of the extensions accepted by IS_RBEXT, IS_SOEXT, or
|
171 |
|
IS_DLEXT. Further, if `ext && rb` then `IS_RBEXT(e)`,
|
172 |
|
and if `ext && !rb` then `IS_SOEXT(e) || IS_DLEXT(e)`.
|
173 |
|
|
174 |
|
If `expanded`, then only the latter form (without
|
175 |
|
load_path[j]) is accepted. Otherwise either form is
|
176 |
|
accepted, *unless* `ext` is false and an otherwise-matching
|
177 |
|
entry of the first form is preceded by an entry of the form
|
178 |
|
"#{features[i2]}" == "#{load_path[j2]}/#{feature}#{e2}"
|
179 |
|
where `e2` matches /^\.[^./]*$/ but is not an allowed extension.
|
180 |
|
After a "distractor" entry of this form, only entries of the
|
181 |
|
form "#{feature}#{e}" are accepted.
|
182 |
|
*/
|
183 |
|
|
184 |
|
v = RARRAY_PTR(features)[i];
|
|
261 |
features_index = get_loaded_features_index();
|
|
262 |
|
|
263 |
feature_val = rb_str_new(feature, len);
|
|
264 |
this_feature_index = rb_hash_lookup(features_index, feature_val);
|
|
265 |
/* We search `features` for an entry such that either
|
|
266 |
"#{features[i]}" == "#{load_path[j]}/#{feature}#{e}"
|
|
267 |
for some j, or
|
|
268 |
"#{features[i]}" == "#{feature}#{e}"
|
|
269 |
Here `e` is an "allowed" extension -- either empty or one
|
|
270 |
of the extensions accepted by IS_RBEXT, IS_SOEXT, or
|
|
271 |
IS_DLEXT. Further, if `ext && rb` then `IS_RBEXT(e)`,
|
|
272 |
and if `ext && !rb` then `IS_SOEXT(e) || IS_DLEXT(e)`.
|
|
273 |
|
|
274 |
If `expanded`, then only the latter form (without load_path[j])
|
|
275 |
is accepted. Otherwise either form is accepted, *unless* `ext`
|
|
276 |
is false and an otherwise-matching entry of the first form is
|
|
277 |
preceded by an entry of the form
|
|
278 |
"#{features[i2]}" == "#{load_path[j2]}/#{feature}#{e2}"
|
|
279 |
where `e2` matches %r{^\.[^./]*$} but is not an allowed extension.
|
|
280 |
After a "distractor" entry of this form, only entries of the
|
|
281 |
form "#{feature}#{e}" are accepted.
|
|
282 |
|
|
283 |
In `rb_provide_feature()` and `get_loaded_features_index()` we
|
|
284 |
maintain an invariant that the array `this_feature_index` will
|
|
285 |
point to every entry in `features` which has the form
|
|
286 |
"#{prefix}#{feature}#{e}"
|
|
287 |
where `e` is empty or matches %r{^\.[^./]*$}, and `prefix` is empty
|
|
288 |
or ends in '/'. This includes both match forms above, as well
|
|
289 |
as any distractors, so we may ignore all other entries in `features`.
|
|
290 |
*/
|
|
291 |
for (i = 0; this_feature_index != Qnil && i < RARRAY_LEN(this_feature_index); i++) {
|
|
292 |
long index = FIX2LONG(rb_ary_entry(this_feature_index, i));
|
|
293 |
v = RARRAY_PTR(features)[index];
|
185 |
294 |
f = StringValuePtr(v);
|
186 |
295 |
if ((n = RSTRING_LEN(v)) < len) continue;
|
187 |
296 |
if (strncmp(f, feature, len) != 0) {
|
... | ... | |
204 |
313 |
return 'r';
|
205 |
314 |
}
|
206 |
315 |
}
|
|
316 |
|
207 |
317 |
loading_tbl = get_loading_table();
|
208 |
318 |
if (loading_tbl) {
|
209 |
319 |
f = 0;
|
... | ... | |
283 |
393 |
static void
|
284 |
394 |
rb_provide_feature(VALUE feature)
|
285 |
395 |
{
|
286 |
|
if (OBJ_FROZEN(get_loaded_features())) {
|
|
396 |
VALUE features;
|
|
397 |
|
|
398 |
features = get_loaded_features();
|
|
399 |
if (OBJ_FROZEN(features)) {
|
287 |
400 |
rb_raise(rb_eRuntimeError,
|
288 |
401 |
"$LOADED_FEATURES is frozen; cannot append feature");
|
289 |
402 |
}
|
290 |
|
rb_ary_push(get_loaded_features(), feature);
|
|
403 |
rb_str_freeze(feature);
|
|
404 |
|
|
405 |
rb_ary_push(features, feature);
|
|
406 |
features_index_add(feature, INT2FIX(RARRAY_LEN(features)-1));
|
|
407 |
reset_loaded_features_snapshot();
|
291 |
408 |
}
|
292 |
409 |
|
293 |
410 |
void
|
... | ... | |
828 |
945 |
rb_define_virtual_variable("$\"", get_loaded_features, 0);
|
829 |
946 |
rb_define_virtual_variable("$LOADED_FEATURES", get_loaded_features, 0);
|
830 |
947 |
vm->loaded_features = rb_ary_new();
|
|
948 |
vm->loaded_features_snapshot = rb_ary_new();
|
|
949 |
vm->loaded_features_index = rb_hash_new();
|
831 |
950 |
|
832 |
951 |
rb_define_global_function("load", rb_f_load, -1);
|
833 |
952 |
rb_define_global_function("require", rb_f_require, 1);
|