mirror of
https://github.com/mruby/mruby
synced 2026-06-08 16:11:16 +00:00
1472 lines
38 KiB
C
1472 lines
38 KiB
C
/*
|
|
** set.c - Set class
|
|
**
|
|
** See Copyright Notice in mruby.h
|
|
*/
|
|
|
|
#include <mruby.h>
|
|
#include <mruby/array.h>
|
|
#include <mruby/class.h>
|
|
#include <mruby/hash.h>
|
|
#include <mruby/string.h>
|
|
#include <mruby/variable.h>
|
|
#include <mruby/proc.h>
|
|
#include <mruby/data.h>
|
|
#include <mruby/internal.h>
|
|
#include <mruby/presym.h>
|
|
#include <mruby/khash.h>
|
|
|
|
KHASH_DECLARE(set, mrb_value, char, FALSE)
|
|
KHASH_DEFINE(set, mrb_value, char, FALSE, mrb_obj_hash_code, mrb_eql)
|
|
|
|
struct RSet {
|
|
MRB_OBJECT_HEADER;
|
|
khash_t(set) *kh;
|
|
};
|
|
|
|
static void
|
|
set_copy_elements(mrb_state *mrb, khash_t(set) *target_kh, khash_t(set) *source_kh)
|
|
{
|
|
if (!source_kh || !target_kh) return;
|
|
|
|
int ai = mrb_gc_arena_save(mrb);
|
|
KHASH_FOREACH(mrb, source_kh, k) {
|
|
kh_put(set, mrb, target_kh, kh_key(source_kh, k));
|
|
mrb_gc_arena_restore(mrb, ai);
|
|
}
|
|
}
|
|
|
|
#define mrb_set_ptr(o) ((struct RSet*)mrb_obj_ptr(o))
|
|
static void
|
|
set_set_khash(mrb_state *mrb, mrb_value self, khash_t(set) *kh)
|
|
{
|
|
mrb_check_type(mrb, self, MRB_TT_SET);
|
|
struct RSet *set = mrb_set_ptr(self);
|
|
set->kh = kh;
|
|
}
|
|
|
|
static khash_t(set) *
|
|
set_get_khash(mrb_state *mrb, mrb_value self)
|
|
{
|
|
mrb_check_type(mrb, self, MRB_TT_SET);
|
|
return mrb_set_ptr(self)->kh;
|
|
}
|
|
|
|
/* Mark function for Set instances */
|
|
size_t
|
|
mrb_gc_mark_set(mrb_state *mrb, struct RBasic *obj)
|
|
{
|
|
struct RSet *s = (struct RSet*)obj;
|
|
khash_t(set) *kh = s->kh;
|
|
if (!kh) return 0;
|
|
|
|
KHASH_FOREACH(mrb, kh, k) {
|
|
if (kh_exist(kh, k)) {
|
|
mrb_gc_mark_value(mrb, kh_key(kh, k));
|
|
}
|
|
}
|
|
return kh_size(kh);
|
|
}
|
|
|
|
void
|
|
mrb_gc_free_set(mrb_state *mrb, struct RBasic *obj)
|
|
{
|
|
struct RSet *s = (struct RSet*)obj;
|
|
if (s->kh) {
|
|
khash_t(set) *kh = s->kh;
|
|
if (kh) {
|
|
kh_destroy(set, mrb, kh);
|
|
}
|
|
}
|
|
}
|
|
|
|
size_t
|
|
mrb_set_memsize(mrb_value set)
|
|
{
|
|
|
|
size_t size = mrb_objspace_page_slot_size();
|
|
struct RSet *s = mrb_set_ptr(set);
|
|
if (s->kh) {
|
|
size += kh_size(s->kh) * sizeof(mrb_value);
|
|
}
|
|
return size;
|
|
}
|
|
|
|
/* Helper function to check if a value is a Set and return a boolean result */
|
|
static mrb_bool
|
|
set_is_set(mrb_state *mrb, mrb_value obj)
|
|
{
|
|
return mrb_obj_is_kind_of(mrb, obj, mrb_class_get(mrb, "Set"));
|
|
}
|
|
|
|
/* Helper function to check if a value is a Set and raise an error if not */
|
|
static void
|
|
set_check_type(mrb_state *mrb, mrb_value obj)
|
|
{
|
|
if (!set_is_set(mrb, obj)) {
|
|
mrb_raise(mrb, E_ARGUMENT_ERROR, "value must be a set");
|
|
}
|
|
}
|
|
|
|
static mrb_value
|
|
set_init(mrb_state *mrb, mrb_value self)
|
|
{
|
|
khash_t(set) *kh = kh_init(set, mrb);
|
|
set_set_khash(mrb, self, kh);
|
|
return self;
|
|
}
|
|
|
|
/*
|
|
* call-seq:
|
|
* set.initialize_copy(orig)
|
|
* Copy constructor.
|
|
*/
|
|
static mrb_value
|
|
set_init_copy(mrb_state *mrb, mrb_value self)
|
|
{
|
|
mrb_value orig = mrb_get_arg1(mrb);
|
|
khash_t(set) *kh;
|
|
|
|
if (mrb_type(orig) != MRB_TT_SET) {
|
|
mrb_raise(mrb, E_TYPE_ERROR, "initialize_copy should take a Set object");
|
|
}
|
|
if (mrb_obj_class(mrb, self) != mrb_obj_class(mrb, orig)) {
|
|
mrb_raise(mrb, E_TYPE_ERROR, "initialize_copy should take same class object");
|
|
}
|
|
|
|
kh = set_get_khash(mrb, orig);
|
|
if (!kh) {
|
|
mrb_raise(mrb, E_ARGUMENT_ERROR, "invalid Set object");
|
|
}
|
|
|
|
kh = kh_copy(set, mrb, kh);
|
|
set_set_khash(mrb, self, kh);
|
|
|
|
return self;
|
|
}
|
|
|
|
/*
|
|
* call-seq:
|
|
* set.size -> integer
|
|
* set.length -> integer
|
|
*
|
|
* Returns the number of elements.
|
|
*/
|
|
static mrb_value
|
|
set_size(mrb_state *mrb, mrb_value self)
|
|
{
|
|
khash_t(set) *kh = set_get_khash(mrb, self);
|
|
if (!kh) return mrb_fixnum_value(0);
|
|
return mrb_fixnum_value(kh_size(kh));
|
|
}
|
|
|
|
/*
|
|
* call-seq:
|
|
* set.empty? -> true or false
|
|
*
|
|
* Returns true if the set contains no elements.
|
|
*/
|
|
static mrb_value
|
|
set_empty_p(mrb_state *mrb, mrb_value self)
|
|
{
|
|
khash_t(set) *kh = set_get_khash(mrb, self);
|
|
if (!kh) return mrb_true_value();
|
|
return mrb_bool_value(kh_size(kh) == 0);
|
|
}
|
|
|
|
/*
|
|
* call-seq:
|
|
* set.clear -> self
|
|
*
|
|
* Removes all elements and returns self.
|
|
*/
|
|
static mrb_value
|
|
set_clear(mrb_state *mrb, mrb_value self)
|
|
{
|
|
khash_t(set) *kh = set_get_khash(mrb, self);
|
|
if (kh) {
|
|
kh_clear(set, mrb, kh);
|
|
}
|
|
return self;
|
|
}
|
|
|
|
/*
|
|
* call-seq:
|
|
* set.to_a -> array
|
|
*
|
|
* Converts the set to an array.
|
|
*/
|
|
static mrb_value
|
|
set_to_a(mrb_state *mrb, mrb_value self)
|
|
{
|
|
khash_t(set) *kh = set_get_khash(mrb, self);
|
|
|
|
if (!kh) return mrb_ary_new(mrb);
|
|
|
|
mrb_value ary = mrb_ary_new_capa(mrb, kh_size(kh));
|
|
|
|
int ai = mrb_gc_arena_save(mrb);
|
|
KHASH_FOREACH(mrb, kh, k) {
|
|
mrb_ary_push(mrb, ary, kh_key(kh, k));
|
|
mrb_gc_arena_restore(mrb, ai);
|
|
}
|
|
|
|
return ary;
|
|
}
|
|
|
|
/*
|
|
* call-seq:
|
|
* set.include?(object) -> true or false
|
|
* set.member?(object) -> true or false
|
|
* set === object -> true or false
|
|
*
|
|
* Returns true if the set contains the given object.
|
|
*/
|
|
static mrb_value
|
|
set_include_p(mrb_state *mrb, mrb_value self)
|
|
{
|
|
mrb_value obj = mrb_get_arg1(mrb);
|
|
khash_t(set) *kh;
|
|
khiter_t k;
|
|
kh = set_get_khash(mrb, self);
|
|
if (!kh) return mrb_false_value();
|
|
|
|
k = kh_get(set, mrb, kh, obj);
|
|
return mrb_bool_value(k != kh_end(kh));
|
|
}
|
|
|
|
/*
|
|
* call-seq:
|
|
* set.add(object) -> self
|
|
* set << object -> self
|
|
*
|
|
* Adds the given object to the set and returns self.
|
|
*/
|
|
static mrb_value
|
|
set_add(mrb_state *mrb, mrb_value self)
|
|
{
|
|
mrb_value obj = mrb_get_arg1(mrb);
|
|
khash_t(set) *kh = set_get_khash(mrb, self);
|
|
if (!kh) {
|
|
mrb_raise(mrb, E_RUNTIME_ERROR, "uninitialized Set");
|
|
}
|
|
|
|
kh_put(set, mrb, kh, obj);
|
|
return self;
|
|
}
|
|
|
|
/*
|
|
* call-seq:
|
|
* set.add?(object) -> self or nil
|
|
*
|
|
* Adds the given object to the set and returns self. If the object is already
|
|
* in the set, returns nil.
|
|
*/
|
|
static mrb_value
|
|
set_add_p(mrb_state *mrb, mrb_value self)
|
|
{
|
|
mrb_value obj = mrb_get_arg1(mrb);
|
|
khash_t(set) *kh = set_get_khash(mrb, self);
|
|
if (!kh) {
|
|
mrb_raise(mrb, E_RUNTIME_ERROR, "uninitialized Set");
|
|
}
|
|
|
|
int ret;
|
|
kh_put2(set, mrb, kh, obj, &ret);
|
|
if (ret == 0) {
|
|
/* Key already exists */
|
|
return mrb_nil_value();
|
|
}
|
|
else {
|
|
/* Key was added */
|
|
return self;
|
|
}
|
|
}
|
|
|
|
/*
|
|
* call-seq:
|
|
* set.delete(object) -> self
|
|
*
|
|
* Deletes the given object from the set and returns self.
|
|
*/
|
|
static mrb_value
|
|
set_delete(mrb_state *mrb, mrb_value self)
|
|
{
|
|
mrb_value obj = mrb_get_arg1(mrb);
|
|
khash_t(set) *kh = set_get_khash(mrb, self);
|
|
if (!kh) return self;
|
|
|
|
khiter_t k = kh_get(set, mrb, kh, obj);
|
|
if (k != kh_end(kh)) {
|
|
kh_del(set, mrb, kh, k);
|
|
}
|
|
return self;
|
|
}
|
|
|
|
/*
|
|
* call-seq:
|
|
* set.delete?(object) -> self or nil
|
|
*
|
|
* Deletes the given object from the set and returns self. If the object is not
|
|
* in the set, returns nil.
|
|
*/
|
|
static mrb_value
|
|
set_delete_p(mrb_state *mrb, mrb_value self)
|
|
{
|
|
mrb_value obj = mrb_get_arg1(mrb);
|
|
khash_t(set) *kh = set_get_khash(mrb, self);
|
|
if (!kh) return mrb_nil_value();
|
|
|
|
khiter_t k = kh_get(set, mrb, kh, obj);
|
|
if (k != kh_end(kh)) {
|
|
kh_del(set, mrb, kh, k);
|
|
return self;
|
|
}
|
|
else {
|
|
return mrb_nil_value();
|
|
}
|
|
}
|
|
|
|
/*
|
|
* Core implementation of Set-to-Set merge (mutating version)
|
|
* This is an internal method that will be called from Ruby
|
|
*/
|
|
static mrb_value
|
|
set_core_merge(mrb_state *mrb, mrb_value self)
|
|
{
|
|
mrb_value other = mrb_get_arg1(mrb);
|
|
|
|
khash_t(set) *self_kh = set_get_khash(mrb, self);
|
|
if (!self_kh) {
|
|
mrb_raise(mrb, E_RUNTIME_ERROR, "uninitialized Set");
|
|
}
|
|
|
|
khash_t(set) *other_kh = set_get_khash(mrb, other);
|
|
if (!other_kh) return self;
|
|
|
|
/* Add all elements from other set */
|
|
set_copy_elements(mrb, self_kh, other_kh);
|
|
|
|
return self;
|
|
}
|
|
|
|
/*
|
|
* Core implementation of Set-to-Set subtraction (mutating version)
|
|
* This is an internal method that will be called from Ruby
|
|
*/
|
|
static mrb_value
|
|
set_core_subtract(mrb_state *mrb, mrb_value self)
|
|
{
|
|
mrb_value other = mrb_get_arg1(mrb);
|
|
|
|
khash_t(set) *self_kh = set_get_khash(mrb, self);
|
|
if (!self_kh) return self;
|
|
|
|
khash_t(set) *other_kh = set_get_khash(mrb, other);
|
|
if (!other_kh) return self;
|
|
|
|
/* Remove all elements that are in other set */
|
|
KHASH_FOREACH(mrb, other_kh, k) {
|
|
mrb_value key = kh_key(other_kh, k);
|
|
khiter_t self_k = kh_get(set, mrb, self_kh, key);
|
|
if (self_k != kh_end(self_kh)) {
|
|
kh_del(set, mrb, self_kh, self_k);
|
|
}
|
|
}
|
|
|
|
return self;
|
|
}
|
|
|
|
/*
|
|
* Core implementation of Set-to-Set union
|
|
* This is an internal method that will be called from Ruby
|
|
*/
|
|
static mrb_value
|
|
set_core_union(mrb_state *mrb, mrb_value self)
|
|
{
|
|
mrb_value other = mrb_get_arg1(mrb);
|
|
|
|
/* Create a new set by duplicating self */
|
|
mrb_value result_set = mrb_obj_dup(mrb, self);
|
|
khash_t(set) *result_kh = set_get_khash(mrb, result_set);
|
|
if (!result_kh) {
|
|
/* If self is empty, create a new empty set */
|
|
result_kh = kh_init(set, mrb);
|
|
set_set_khash(mrb, result_set, result_kh);
|
|
}
|
|
|
|
/* Add all elements from other set */
|
|
khash_t(set) *other_kh = set_get_khash(mrb, other);
|
|
set_copy_elements(mrb, result_kh, other_kh);
|
|
|
|
return result_set;
|
|
}
|
|
|
|
/*
|
|
* Core implementation of Set-to-Set difference
|
|
* This is an internal method that will be called from Ruby
|
|
*/
|
|
static mrb_value
|
|
set_core_difference(mrb_state *mrb, mrb_value self)
|
|
{
|
|
mrb_value other = mrb_get_arg1(mrb);
|
|
|
|
/* Create a new set by duplicating self */
|
|
mrb_value result_set = mrb_obj_dup(mrb, self);
|
|
khash_t(set) *result_kh = set_get_khash(mrb, result_set);
|
|
if (!result_kh) {
|
|
/* If self is empty, return an empty set */
|
|
return result_set;
|
|
}
|
|
|
|
/* Remove all elements that are in other set */
|
|
khash_t(set) *other_kh = set_get_khash(mrb, other);
|
|
if (other_kh) {
|
|
KHASH_FOREACH(mrb, other_kh, k) {
|
|
mrb_value key = kh_key(other_kh, k);
|
|
khiter_t result_k = kh_get(set, mrb, result_kh, key);
|
|
if (result_k != kh_end(result_kh)) {
|
|
kh_del(set, mrb, result_kh, result_k);
|
|
}
|
|
}
|
|
}
|
|
|
|
return result_set;
|
|
}
|
|
|
|
|
|
/*
|
|
* Core implementation of Set-to-Set intersection
|
|
* This is an internal method that will be called from Ruby
|
|
*/
|
|
static mrb_value
|
|
set_core_intersection(mrb_state *mrb, mrb_value self)
|
|
{
|
|
mrb_value other = mrb_get_arg1(mrb);
|
|
|
|
/* Create a new empty set of the same class as self */
|
|
mrb_value result_set = mrb_obj_new(mrb, mrb_obj_class(mrb, self), 0, NULL);
|
|
khash_t(set) *result_kh = set_get_khash(mrb, result_set);
|
|
|
|
khash_t(set) *self_kh = set_get_khash(mrb, self);
|
|
if (!self_kh) return result_set;
|
|
|
|
khash_t(set) *other_kh = set_get_khash(mrb, other);
|
|
if (!other_kh) return result_set;
|
|
|
|
KHASH_FOREACH(mrb, other_kh, k) {
|
|
mrb_value key = kh_key(other_kh, k);
|
|
khiter_t self_k = kh_get(set, mrb, self_kh, key);
|
|
|
|
/* If key exists in self, add it to result */
|
|
if (self_k != kh_end(self_kh)) {
|
|
kh_put(set, mrb, result_kh, key);
|
|
}
|
|
}
|
|
|
|
return result_set;
|
|
}
|
|
|
|
|
|
/*
|
|
* Core implementation of Set-to-Set XOR (symmetric difference)
|
|
* This is an internal method that will be called from Ruby
|
|
*/
|
|
static mrb_value
|
|
set_core_xor(mrb_state *mrb, mrb_value self)
|
|
{
|
|
mrb_value other = mrb_get_arg1(mrb);
|
|
|
|
/* Create a new empty set of the same class as self */
|
|
mrb_value result_set = mrb_obj_new(mrb, mrb_obj_class(mrb, self), 0, NULL);
|
|
khash_t(set) *result_kh = set_get_khash(mrb, result_set);
|
|
khash_t(set) *self_kh = set_get_khash(mrb, self);
|
|
khash_t(set) *other_kh = set_get_khash(mrb, other);
|
|
|
|
if (!self_kh || kh_size(self_kh) == 0) {
|
|
/* If self is empty, return a copy of other */
|
|
if (other_kh) {
|
|
set_copy_elements(mrb, result_kh, other_kh);
|
|
}
|
|
return result_set;
|
|
}
|
|
|
|
if (!other_kh || kh_size(other_kh) == 0) {
|
|
/* If other is empty, return a copy of self */
|
|
set_copy_elements(mrb, result_kh, self_kh);
|
|
return result_set;
|
|
}
|
|
|
|
/* Add elements from self that are not in other */
|
|
int ai = mrb_gc_arena_save(mrb);
|
|
KHASH_FOREACH(mrb, self_kh, k) {
|
|
mrb_value key = kh_key(self_kh, k);
|
|
khiter_t other_k = kh_get(set, mrb, other_kh, key);
|
|
|
|
/* Add to result if not in other */
|
|
if (other_k == kh_end(other_kh)) {
|
|
kh_put(set, mrb, result_kh, key);
|
|
}
|
|
mrb_gc_arena_restore(mrb, ai);
|
|
}
|
|
|
|
/* Add elements from other that are not in self */
|
|
KHASH_FOREACH(mrb, other_kh, k) {
|
|
mrb_value key = kh_key(other_kh, k);
|
|
khiter_t self_k = kh_get(set, mrb, self_kh, key);
|
|
|
|
/* Add to result if not in self */
|
|
if (self_k == kh_end(self_kh)) {
|
|
kh_put(set, mrb, result_kh, key);
|
|
}
|
|
mrb_gc_arena_restore(mrb, ai);
|
|
}
|
|
|
|
return result_set;
|
|
}
|
|
|
|
/*
|
|
* call-seq:
|
|
* set == other -> true or false
|
|
*
|
|
* Returns true if two sets are equal.
|
|
*/
|
|
/*
|
|
* call-seq:
|
|
* set.eql?(other) -> true or false
|
|
*
|
|
* Returns true if two sets are equal.
|
|
*/
|
|
static mrb_value
|
|
set_equal(mrb_state *mrb, mrb_value self)
|
|
{
|
|
mrb_value other = mrb_get_arg1(mrb);
|
|
|
|
/* Fast path: same object */
|
|
if (mrb_obj_equal(mrb, self, other)) {
|
|
return mrb_true_value();
|
|
}
|
|
|
|
/* Only compare with other Set objects */
|
|
if (!set_is_set(mrb, other)) {
|
|
return mrb_false_value();
|
|
}
|
|
|
|
khash_t(set) *kh1 = set_get_khash(mrb, self);
|
|
khash_t(set) *kh2 = set_get_khash(mrb, other);
|
|
|
|
/* Fast path: both empty */
|
|
if ((!kh1 || kh_size(kh1) == 0) && (!kh2 || kh_size(kh2) == 0)) {
|
|
return mrb_true_value();
|
|
}
|
|
|
|
/* Fast path: different sizes */
|
|
if (!kh1 || !kh2 || kh_size(kh1) != kh_size(kh2)) {
|
|
return mrb_false_value();
|
|
}
|
|
|
|
/* Compare elements: iterate through the smaller hash for efficiency */
|
|
int ai = mrb_gc_arena_save(mrb);
|
|
KHASH_FOREACH(mrb, kh1, k) {
|
|
khiter_t k2 = kh_get(set, mrb, kh2, kh_key(kh1, k));
|
|
if (k2 == kh_end(kh2)) {
|
|
mrb_gc_arena_restore(mrb, ai);
|
|
return mrb_false_value(); /* Element in self not found in other */
|
|
}
|
|
mrb_gc_arena_restore(mrb, ai);
|
|
}
|
|
|
|
return mrb_true_value();
|
|
}
|
|
|
|
/*
|
|
* call-seq:
|
|
* set.hash -> integer
|
|
*
|
|
* Compute a hash-code for this set.
|
|
* Uses an improved hash algorithm for better distribution.
|
|
*/
|
|
static mrb_value
|
|
set_hash_m(mrb_state *mrb, mrb_value self)
|
|
{
|
|
khash_t(set) *kh = set_get_khash(mrb, self);
|
|
|
|
/* Use FNV-1a hash algorithm with better distribution properties */
|
|
uint64_t hash = 0xcbf29ce484222325ULL; /* FNV offset basis */
|
|
const uint64_t fnv_prime = 0x100000001b3ULL; /* FNV prime */
|
|
|
|
/* Include the size of the set in the hash */
|
|
size_t size = kh ? kh_size(kh) : 0;
|
|
hash ^= size;
|
|
hash *= fnv_prime;
|
|
|
|
if (kh && size > 0) {
|
|
/* Process each element */
|
|
int ai = mrb_gc_arena_save(mrb);
|
|
KHASH_FOREACH(mrb, kh, k) {
|
|
/* Get element's hash code */
|
|
khint_t elem_hash = (khint_t)mrb_obj_hash_code(mrb, kh_key(kh, k));
|
|
|
|
/* Mix using FNV-1a algorithm */
|
|
hash ^= elem_hash;
|
|
hash *= fnv_prime;
|
|
|
|
mrb_gc_arena_restore(mrb, ai);
|
|
}
|
|
}
|
|
|
|
/* Final mixing to improve avalanche effect */
|
|
hash ^= hash >> 32;
|
|
|
|
return mrb_fixnum_value((mrb_int)hash);
|
|
}
|
|
|
|
/*
|
|
* call-seq:
|
|
* set.superset?(other) -> true or false
|
|
* set >= other -> true or false
|
|
*
|
|
* Returns true if the set is a superset of the given set.
|
|
*/
|
|
static mrb_value
|
|
set_superset_p(mrb_state *mrb, mrb_value self)
|
|
{
|
|
mrb_value other = mrb_get_arg1(mrb);
|
|
|
|
/* Check if other is a Set */
|
|
set_check_type(mrb, other);
|
|
|
|
khash_t(set) *self_kh = set_get_khash(mrb, self);
|
|
khash_t(set) *other_kh = set_get_khash(mrb, other);
|
|
|
|
/* Handle empty sets */
|
|
if (!other_kh || kh_size(other_kh) == 0) {
|
|
return mrb_true_value(); /* Empty set is a subset of any set */
|
|
}
|
|
|
|
if (!self_kh) {
|
|
return mrb_false_value(); /* Empty set is not a superset of a non-empty set */
|
|
}
|
|
|
|
/* Check size first - a superset must be at least as large as the subset */
|
|
if (kh_size(self_kh) < kh_size(other_kh)) {
|
|
return mrb_false_value();
|
|
}
|
|
|
|
/* Check if all elements in other are in self */
|
|
int ai = mrb_gc_arena_save(mrb);
|
|
KHASH_FOREACH(mrb, other_kh, k) {
|
|
khiter_t self_k = kh_get(set, mrb, self_kh, kh_key(other_kh, k));
|
|
if (self_k == kh_end(self_kh)) {
|
|
return mrb_false_value(); /* Element in other not found in self */
|
|
}
|
|
mrb_gc_arena_restore(mrb, ai);
|
|
}
|
|
|
|
return mrb_true_value();
|
|
}
|
|
|
|
/*
|
|
* call-seq:
|
|
* set.proper_superset?(other) -> true or false
|
|
* set > other -> true or false
|
|
*
|
|
* Returns true if the set is a proper superset of the given set.
|
|
*/
|
|
static mrb_value
|
|
set_proper_superset_p(mrb_state *mrb, mrb_value self)
|
|
{
|
|
mrb_value other = mrb_get_arg1(mrb);
|
|
|
|
/* Check if other is a Set */
|
|
set_check_type(mrb, other);
|
|
|
|
khash_t(set) *self_kh = set_get_khash(mrb, self);
|
|
khash_t(set) *other_kh = set_get_khash(mrb, other);
|
|
|
|
/* Handle empty sets */
|
|
if (!other_kh || kh_size(other_kh) == 0) {
|
|
/* Empty set is a proper subset of any non-empty set */
|
|
return self_kh && kh_size(self_kh) > 0 ? mrb_true_value() : mrb_false_value();
|
|
}
|
|
|
|
if (!self_kh) {
|
|
return mrb_false_value(); /* Empty set is not a proper superset of any set */
|
|
}
|
|
|
|
/* For a proper superset, self must be strictly larger than other */
|
|
if (kh_size(self_kh) <= kh_size(other_kh)) {
|
|
return mrb_false_value();
|
|
}
|
|
|
|
/* Check if all elements in other are in self */
|
|
int ai = mrb_gc_arena_save(mrb);
|
|
KHASH_FOREACH(mrb, other_kh, k) {
|
|
khiter_t self_k = kh_get(set, mrb, self_kh, kh_key(other_kh, k));
|
|
if (self_k == kh_end(self_kh)) {
|
|
return mrb_false_value(); /* Element in other not found in self */
|
|
}
|
|
mrb_gc_arena_restore(mrb, ai);
|
|
}
|
|
|
|
return mrb_true_value();
|
|
}
|
|
|
|
/*
|
|
* call-seq:
|
|
* set.subset?(other) -> true or false
|
|
* set <= other -> true or false
|
|
*
|
|
* Returns true if the set is a subset of the given set.
|
|
*/
|
|
static mrb_value
|
|
set_subset_p(mrb_state *mrb, mrb_value self)
|
|
{
|
|
mrb_value other = mrb_get_arg1(mrb);
|
|
|
|
/* Check if other is a Set */
|
|
set_check_type(mrb, other);
|
|
|
|
khash_t(set) *self_kh = set_get_khash(mrb, self);
|
|
khash_t(set) *other_kh = set_get_khash(mrb, other);
|
|
|
|
/* Handle empty sets */
|
|
if (!self_kh || kh_size(self_kh) == 0) {
|
|
return mrb_true_value(); /* Empty set is a subset of any set */
|
|
}
|
|
|
|
if (!other_kh) {
|
|
return mrb_false_value(); /* Non-empty set is not a subset of an empty set */
|
|
}
|
|
|
|
/* Check size first - a subset cannot be larger than its superset */
|
|
if (kh_size(other_kh) < kh_size(self_kh)) {
|
|
return mrb_false_value();
|
|
}
|
|
|
|
/* Check if all elements in self are in other */
|
|
int ai = mrb_gc_arena_save(mrb);
|
|
KHASH_FOREACH(mrb, self_kh, k) {
|
|
khiter_t other_k = kh_get(set, mrb, other_kh, kh_key(self_kh, k));
|
|
if (other_k == kh_end(other_kh)) {
|
|
return mrb_false_value(); /* Element in self not found in other */
|
|
}
|
|
mrb_gc_arena_restore(mrb, ai);
|
|
}
|
|
|
|
return mrb_true_value();
|
|
}
|
|
|
|
/*
|
|
* call-seq:
|
|
* set.proper_subset?(other) -> true or false
|
|
* set < other -> true or false
|
|
*
|
|
* Returns true if the set is a proper subset of the given set.
|
|
*/
|
|
static mrb_value
|
|
set_proper_subset_p(mrb_state *mrb, mrb_value self)
|
|
{
|
|
mrb_value other = mrb_get_arg1(mrb);
|
|
|
|
/* Check if other is a Set */
|
|
set_check_type(mrb, other);
|
|
|
|
khash_t(set) *self_kh = set_get_khash(mrb, self);
|
|
khash_t(set) *other_kh = set_get_khash(mrb, other);
|
|
|
|
/* Handle empty sets */
|
|
if (!self_kh || kh_size(self_kh) == 0) {
|
|
/* Empty set is a proper subset of any non-empty set */
|
|
return other_kh && kh_size(other_kh) > 0 ? mrb_true_value() : mrb_false_value();
|
|
}
|
|
|
|
if (!other_kh) {
|
|
return mrb_false_value(); /* Non-empty set is not a proper subset of an empty set */
|
|
}
|
|
|
|
/* For a proper subset, self must be strictly smaller than other */
|
|
if (kh_size(other_kh) <= kh_size(self_kh)) {
|
|
return mrb_false_value();
|
|
}
|
|
|
|
/* Check if all elements in self are in other */
|
|
int ai = mrb_gc_arena_save(mrb);
|
|
KHASH_FOREACH(mrb, self_kh, k) {
|
|
khiter_t other_k = kh_get(set, mrb, other_kh, kh_key(self_kh, k));
|
|
if (other_k == kh_end(other_kh)) {
|
|
return mrb_false_value(); /* Element in self not found in other */
|
|
}
|
|
mrb_gc_arena_restore(mrb, ai);
|
|
}
|
|
|
|
return mrb_true_value();
|
|
}
|
|
|
|
/*
|
|
* call-seq:
|
|
* set.intersect?(other) -> true or false
|
|
*
|
|
* Returns true if the set and the given set have at least one element in common.
|
|
*/
|
|
static mrb_value
|
|
set_intersect_p(mrb_state *mrb, mrb_value self)
|
|
{
|
|
mrb_value other = mrb_get_arg1(mrb);
|
|
|
|
/* Check if other is a Set */
|
|
set_check_type(mrb, other);
|
|
|
|
khash_t(set) *self_kh = set_get_khash(mrb, self);
|
|
khash_t(set) *other_kh = set_get_khash(mrb, other);
|
|
|
|
/* Handle empty sets */
|
|
if (!self_kh || !other_kh || kh_size(self_kh) == 0 || kh_size(other_kh) == 0) {
|
|
return mrb_false_value(); /* Empty sets have no elements in common */
|
|
}
|
|
|
|
/* Iterate through the smaller set for efficiency */
|
|
int ai = mrb_gc_arena_save(mrb);
|
|
if (kh_size(self_kh) < kh_size(other_kh)) {
|
|
KHASH_FOREACH(mrb, self_kh, k) {
|
|
khiter_t other_k = kh_get(set, mrb, other_kh, kh_key(self_kh, k));
|
|
if (other_k != kh_end(other_kh)) {
|
|
return mrb_true_value(); /* Found a common element */
|
|
}
|
|
mrb_gc_arena_restore(mrb, ai);
|
|
}
|
|
} else {
|
|
KHASH_FOREACH(mrb, other_kh, k) {
|
|
khiter_t self_k = kh_get(set, mrb, self_kh, kh_key(other_kh, k));
|
|
if (self_k != kh_end(self_kh)) {
|
|
return mrb_true_value(); /* Found a common element */
|
|
}
|
|
mrb_gc_arena_restore(mrb, ai);
|
|
}
|
|
}
|
|
|
|
return mrb_false_value(); /* No common elements found */
|
|
}
|
|
|
|
/*
|
|
* call-seq:
|
|
* set.disjoint?(other) -> true or false
|
|
*
|
|
* Returns true if the set and the given set have no elements in common.
|
|
*/
|
|
static mrb_value
|
|
set_disjoint_p(mrb_state *mrb, mrb_value self)
|
|
{
|
|
mrb_value result = set_intersect_p(mrb, self);
|
|
return mrb_bool_value(!mrb_test(result));
|
|
}
|
|
|
|
/*
|
|
* call-seq:
|
|
* set <=> other -> -1, 0, +1, or nil
|
|
*
|
|
* Compares this set with another set.
|
|
* Returns -1 if this set is a proper subset of the other set,
|
|
* +1 if this set is a proper superset of the other set,
|
|
* 0 if the sets are equal,
|
|
* or nil if the sets cannot be compared (they are neither subsets nor supersets).
|
|
*/
|
|
static mrb_value
|
|
set_cmp(mrb_state *mrb, mrb_value self)
|
|
{
|
|
mrb_value other = mrb_get_arg1(mrb);
|
|
|
|
if (!set_is_set(mrb, other)) {
|
|
return mrb_nil_value();
|
|
}
|
|
|
|
khash_t(set) *self_kh = set_get_khash(mrb, self);
|
|
khash_t(set) *other_kh = set_get_khash(mrb, other);
|
|
|
|
/* Handle empty sets */
|
|
if (!self_kh || kh_size(self_kh) == 0) {
|
|
if (!other_kh || kh_size(other_kh) == 0) {
|
|
return mrb_fixnum_value(0); /* Both empty, they're equal */
|
|
}
|
|
return mrb_fixnum_value(-1); /* Empty set is a proper subset of any non-empty set */
|
|
}
|
|
|
|
if (!other_kh || kh_size(other_kh) == 0) {
|
|
return mrb_fixnum_value(1); /* Any non-empty set is a proper superset of an empty set */
|
|
}
|
|
|
|
/* Compare sizes */
|
|
int size_cmp = kh_size(self_kh) - kh_size(other_kh);
|
|
|
|
if (size_cmp < 0) {
|
|
/* self might be a proper subset of other */
|
|
int ai = mrb_gc_arena_save(mrb);
|
|
KHASH_FOREACH(mrb, self_kh, k) {
|
|
khiter_t other_k = kh_get(set, mrb, other_kh, kh_key(self_kh, k));
|
|
if (other_k == kh_end(other_kh)) {
|
|
/* Not a subset */
|
|
mrb_gc_arena_restore(mrb, ai);
|
|
return mrb_nil_value(); /* Not comparable */
|
|
}
|
|
mrb_gc_arena_restore(mrb, ai);
|
|
}
|
|
|
|
/* All elements of self are in other, and self is smaller than other */
|
|
return mrb_fixnum_value(-1); /* self is a proper subset of other */
|
|
}
|
|
else if (size_cmp > 0) {
|
|
/* self might be a proper superset of other */
|
|
int ai = mrb_gc_arena_save(mrb);
|
|
KHASH_FOREACH(mrb, other_kh, k) {
|
|
khiter_t self_k = kh_get(set, mrb, self_kh, kh_key(other_kh, k));
|
|
if (self_k == kh_end(self_kh)) {
|
|
/* Not a superset */
|
|
mrb_gc_arena_restore(mrb, ai);
|
|
return mrb_nil_value(); /* Not comparable */
|
|
}
|
|
mrb_gc_arena_restore(mrb, ai);
|
|
}
|
|
|
|
/* All elements of other are in self, and self is larger than other */
|
|
return mrb_fixnum_value(1); /* self is a proper superset of other */
|
|
}
|
|
else {
|
|
/* Same size, check if they're equal */
|
|
mrb_bool is_equal = TRUE;
|
|
|
|
int ai3 = mrb_gc_arena_save(mrb);
|
|
KHASH_FOREACH(mrb, self_kh, k) {
|
|
khiter_t other_k = kh_get(set, mrb, other_kh, kh_key(self_kh, k));
|
|
if (other_k == kh_end(other_kh)) {
|
|
is_equal = FALSE;
|
|
break;
|
|
}
|
|
mrb_gc_arena_restore(mrb, ai3);
|
|
}
|
|
|
|
if (is_equal) {
|
|
return mrb_fixnum_value(0); /* Sets are equal */
|
|
}
|
|
}
|
|
|
|
/* Sets are not comparable */
|
|
return mrb_nil_value();
|
|
}
|
|
|
|
/*
|
|
* call-seq:
|
|
* set.join(separator = nil) -> string
|
|
*
|
|
* Returns a string created by converting each element of the set to a string,
|
|
* separated by the given separator.
|
|
*/
|
|
static mrb_value
|
|
set_join(mrb_state *mrb, mrb_value self)
|
|
{
|
|
mrb_value separator = mrb_nil_value();
|
|
mrb_get_args(mrb, "|S", &separator);
|
|
|
|
khash_t(set) *kh = set_get_khash(mrb, self);
|
|
if (!kh || kh_size(kh) == 0) {
|
|
return mrb_str_new_lit(mrb, "");
|
|
}
|
|
|
|
/* Get separator string */
|
|
const char *sep_ptr = "";
|
|
mrb_int sep_len = 0;
|
|
if (!mrb_nil_p(separator)) {
|
|
sep_ptr = RSTRING_PTR(separator);
|
|
sep_len = RSTRING_LEN(separator);
|
|
}
|
|
|
|
/* Create result string */
|
|
mrb_value result = mrb_str_new_capa(mrb, 64); /* Initial capacity */
|
|
mrb_bool first = TRUE;
|
|
|
|
/* Iterate through all elements */
|
|
int ai = mrb_gc_arena_save(mrb);
|
|
KHASH_FOREACH(mrb, kh, k) {
|
|
/* Add separator between elements */
|
|
if (!first) {
|
|
mrb_str_cat(mrb, result, sep_ptr, sep_len);
|
|
} else {
|
|
first = FALSE;
|
|
}
|
|
|
|
/* Convert element to string and append */
|
|
mrb_value elem = kh_key(kh, k);
|
|
mrb_value str = mrb_obj_as_string(mrb, elem);
|
|
mrb_str_cat_str(mrb, result, str);
|
|
|
|
/* Manage GC arena to prevent memory leaks */
|
|
mrb_gc_arena_restore(mrb, ai);
|
|
}
|
|
|
|
return result;
|
|
}
|
|
|
|
/*
|
|
* call-seq:
|
|
* set.inspect -> string
|
|
* set.to_s -> string
|
|
*
|
|
* Returns a string representation of the set.
|
|
* Format: Set[elem1, elem2, ...]
|
|
*/
|
|
static mrb_value
|
|
set_inspect(mrb_state *mrb, mrb_value self)
|
|
{
|
|
struct RClass* c = mrb_obj_class(mrb, self);
|
|
const char* classname = mrb_class_name(mrb, c);
|
|
khash_t(set) *kh = set_get_khash(mrb, self);
|
|
|
|
/* Handle empty set */
|
|
if (!kh || kh_size(kh) == 0) {
|
|
return mrb_format(mrb, "%s[]", classname);
|
|
}
|
|
|
|
/* Handle recursive inspection */
|
|
if (mrb_inspect_recursive_p(mrb, self)) {
|
|
return mrb_format(mrb, "%s[...]", classname);
|
|
}
|
|
|
|
/* Estimate buffer size based on set size */
|
|
size_t size = kh_size(kh);
|
|
size_t buffer_size = 16 + strlen(classname) + (size * 8); /* Rough estimate */
|
|
|
|
/* Create the beginning of the string with pre-allocated capacity */
|
|
mrb_value result_str = mrb_str_new_capa(mrb, buffer_size);
|
|
mrb_str_cat_cstr(mrb, result_str, classname);
|
|
mrb_str_cat_lit(mrb, result_str, "[");
|
|
|
|
/* Iterate through all elements */
|
|
mrb_bool first = TRUE;
|
|
int ai = mrb_gc_arena_save(mrb);
|
|
|
|
KHASH_FOREACH(mrb, kh, k) {
|
|
/* Add comma between elements */
|
|
if (!first) {
|
|
mrb_str_cat_lit(mrb, result_str, ", ");
|
|
} else {
|
|
first = FALSE;
|
|
}
|
|
|
|
/* Get element and its string representation */
|
|
mrb_value elem = kh_key(kh, k);
|
|
mrb_value entry_str = mrb_inspect(mrb, elem);
|
|
mrb_str_cat_str(mrb, result_str, entry_str);
|
|
|
|
mrb_gc_arena_restore(mrb, ai);
|
|
}
|
|
|
|
/* Add the closing part */
|
|
mrb_str_cat_lit(mrb, result_str, "]");
|
|
|
|
return result_str;
|
|
}
|
|
|
|
/*
|
|
* call-seq:
|
|
* set.reset -> self
|
|
*
|
|
* Resets the internal state after modification to existing elements.
|
|
* This is necessary when the hash value of objects in the set has changed.
|
|
* It rebuilds the hash table to ensure all elements can be found.
|
|
*/
|
|
static mrb_value
|
|
set_reset(mrb_state *mrb, mrb_value self)
|
|
{
|
|
mrb_check_frozen_value(mrb, self);
|
|
|
|
khash_t(set) *old_kh = set_get_khash(mrb, self);
|
|
if (old_kh && kh_size(old_kh) > 0) {
|
|
/* Create a new hash table by copying the old one */
|
|
khash_t(set) *new_kh = kh_copy(set, mrb, old_kh);
|
|
|
|
/* Replace the old table with the new one */
|
|
set_set_khash(mrb, self, new_kh);
|
|
|
|
/* Destroy the old table */
|
|
kh_destroy(set, mrb, old_kh);
|
|
}
|
|
|
|
return self;
|
|
}
|
|
|
|
/*
|
|
* call-seq:
|
|
* set.add_all(*objects) -> self
|
|
*
|
|
* Adds multiple objects to the set and returns self.
|
|
*/
|
|
static mrb_value
|
|
set_add_all(mrb_state *mrb, mrb_value self)
|
|
{
|
|
const mrb_value *argv;
|
|
mrb_int argc;
|
|
|
|
mrb_get_args(mrb, "*", &argv, &argc);
|
|
khash_t(set) *kh = set_get_khash(mrb, self);
|
|
if (!kh) {
|
|
mrb_raise(mrb, E_RUNTIME_ERROR, "uninitialized Set");
|
|
}
|
|
|
|
int ai = mrb_gc_arena_save(mrb);
|
|
for (mrb_int i = 0; i < argc; i++) {
|
|
kh_put(set, mrb, kh, argv[i]);
|
|
mrb_gc_arena_restore(mrb, ai);
|
|
}
|
|
|
|
return self;
|
|
}
|
|
|
|
/*
|
|
* Optimized implementation for flattening sets
|
|
* Uses a more efficient algorithm with minimal memory usage
|
|
*/
|
|
|
|
/* Small array for tracking seen object IDs to detect cycles */
|
|
#define MAX_NESTED_DEPTH 16
|
|
|
|
/*
|
|
* Recursively flattens a set by merging nested sets into the target set.
|
|
* This is an internal helper function that does not call back to the VM.
|
|
*
|
|
* @param mrb The mruby state
|
|
* @param target_kh The target hash table to add elements to
|
|
* @param source_kh The source hash table to flatten
|
|
* @param seen_count Pointer to the current count of seen sets (recursion depth)
|
|
* @return 0 on success, -1 if recursion depth exceeds maximum
|
|
*/
|
|
static int
|
|
set_flatten_recursive(mrb_state *mrb, khash_t(set) *target_kh, khash_t(set) *source_kh,
|
|
int *seen_count)
|
|
{
|
|
if (!source_kh || !target_kh) return 0;
|
|
if (*seen_count >= MAX_NESTED_DEPTH) return -1;
|
|
|
|
int ai = mrb_gc_arena_save(mrb);
|
|
|
|
/* Process each element in the source set */
|
|
KHASH_FOREACH(mrb, source_kh, k) {
|
|
mrb_value elem = kh_key(source_kh, k);
|
|
|
|
/* Check if element is a Set */
|
|
if (set_is_set(mrb, elem)) {
|
|
/* Increment recursion depth */
|
|
(*seen_count)++;
|
|
|
|
/* Recursively flatten the nested set */
|
|
khash_t(set) *nested_kh = set_get_khash(mrb, elem);
|
|
if (nested_kh) {
|
|
int nested_result = set_flatten_recursive(mrb, target_kh, nested_kh, seen_count);
|
|
if (nested_result < 0) {
|
|
return nested_result; /* Propagate error code */
|
|
}
|
|
}
|
|
|
|
/* Decrement recursion depth */
|
|
(*seen_count)--;
|
|
} else {
|
|
/* Add non-Set element directly */
|
|
kh_put(set, mrb, target_kh, elem);
|
|
}
|
|
}
|
|
|
|
mrb_gc_arena_restore(mrb, ai);
|
|
return 0;
|
|
}
|
|
|
|
/*
|
|
* call-seq:
|
|
* set.flatten -> new_set
|
|
*
|
|
* Returns a new set that is a flattened version of this set.
|
|
* Recursively flattens nested sets.
|
|
*/
|
|
static mrb_value
|
|
set_flatten(mrb_state *mrb, mrb_value self)
|
|
{
|
|
khash_t(set) *self_kh = set_get_khash(mrb, self);
|
|
|
|
/* Fast path for empty sets */
|
|
if (!self_kh || kh_size(self_kh) == 0) {
|
|
return mrb_obj_new(mrb, mrb_obj_class(mrb, self), 0, NULL);
|
|
}
|
|
|
|
/* Fast path: check if there are any nested sets */
|
|
mrb_bool has_nested_sets = FALSE;
|
|
int ai = mrb_gc_arena_save(mrb);
|
|
KHASH_FOREACH(mrb, self_kh, k) {
|
|
if (set_is_set(mrb, kh_key(self_kh, k))) {
|
|
has_nested_sets = TRUE;
|
|
break;
|
|
}
|
|
}
|
|
mrb_gc_arena_restore(mrb, ai);
|
|
|
|
/* If no nested sets, just return a duplicate */
|
|
if (!has_nested_sets) {
|
|
return mrb_obj_dup(mrb, self);
|
|
}
|
|
|
|
/* Create a new set of the same class */
|
|
mrb_value result_set = mrb_obj_new(mrb, mrb_obj_class(mrb, self), 0, NULL);
|
|
khash_t(set) *result_kh = set_get_khash(mrb, result_set);
|
|
|
|
/* Track recursion depth */
|
|
int seen_count = 0;
|
|
|
|
/* Flatten the set */
|
|
if (set_flatten_recursive(mrb, result_kh, self_kh, &seen_count) < 0) {
|
|
mrb_raise(mrb, E_ARGUMENT_ERROR, "flatten recursion depth too deep");
|
|
}
|
|
|
|
return result_set;
|
|
}
|
|
|
|
/*
|
|
* call-seq:
|
|
* set.flatten! -> self or nil
|
|
*
|
|
* Replaces the contents of this set with a flattened version of itself.
|
|
* Returns self if flattened, nil if no changes were made.
|
|
*/
|
|
static mrb_value
|
|
set_flatten_bang(mrb_state *mrb, mrb_value self)
|
|
{
|
|
mrb_check_frozen_value(mrb, self);
|
|
|
|
khash_t(set) *self_kh = set_get_khash(mrb, self);
|
|
if (!self_kh || kh_size(self_kh) == 0) {
|
|
return mrb_nil_value(); /* No changes needed for empty set */
|
|
}
|
|
|
|
/* First, check if there are any nested sets */
|
|
mrb_bool has_nested_sets = FALSE;
|
|
int ai = mrb_gc_arena_save(mrb);
|
|
KHASH_FOREACH(mrb, self_kh, k) {
|
|
mrb_value elem = kh_key(self_kh, k);
|
|
if (set_is_set(mrb, elem)) {
|
|
has_nested_sets = TRUE;
|
|
break;
|
|
}
|
|
}
|
|
mrb_gc_arena_restore(mrb, ai);
|
|
|
|
if (!has_nested_sets) {
|
|
return mrb_nil_value(); /* No nested sets, no changes needed */
|
|
}
|
|
|
|
/* Create a temporary hash table for the flattened result */
|
|
khash_t(set) *new_kh = kh_init(set, mrb);
|
|
|
|
/* Track recursion depth */
|
|
int seen_count = 0;
|
|
|
|
/* Flatten the set into the new hash table */
|
|
if (set_flatten_recursive(mrb, new_kh, self_kh, &seen_count) < 0) {
|
|
/* Clean up the new hash table if an error occurred */
|
|
kh_destroy(set, mrb, new_kh);
|
|
|
|
/* Raise appropriate exception */
|
|
mrb_raise(mrb, E_ARGUMENT_ERROR, "flatten recursion depth too deep");
|
|
}
|
|
|
|
/* Replace the old hash table with the new one */
|
|
set_set_khash(mrb, self, new_kh);
|
|
|
|
/* Clean up the old hash table */
|
|
kh_destroy(set, mrb, self_kh);
|
|
|
|
return self;
|
|
}
|
|
|
|
/*
|
|
* call-seq:
|
|
* set.delete_all(*objects) -> self
|
|
*
|
|
* Deletes multiple objects from the set and returns self.
|
|
*/
|
|
static mrb_value
|
|
set_delete_all(mrb_state *mrb, mrb_value self)
|
|
{
|
|
const mrb_value *argv;
|
|
mrb_int argc;
|
|
|
|
mrb_get_args(mrb, "*", &argv, &argc);
|
|
khash_t(set) *kh = set_get_khash(mrb, self);
|
|
if (!kh) return self;
|
|
|
|
int ai = mrb_gc_arena_save(mrb);
|
|
for (mrb_int i = 0; i < argc; i++) {
|
|
khiter_t k = kh_get(set, mrb, kh, argv[i]);
|
|
if (k != kh_end(kh)) {
|
|
kh_del(set, mrb, kh, k);
|
|
}
|
|
mrb_gc_arena_restore(mrb, ai);
|
|
}
|
|
|
|
return self;
|
|
}
|
|
|
|
/*
|
|
* call-seq:
|
|
* set.include_all?(*objects) -> true or false
|
|
*
|
|
* Returns true if the set contains all of the given objects.
|
|
*/
|
|
static mrb_value
|
|
set_include_all_p(mrb_state *mrb, mrb_value self)
|
|
{
|
|
const mrb_value *argv;
|
|
mrb_int argc;
|
|
|
|
mrb_get_args(mrb, "*", &argv, &argc);
|
|
khash_t(set) *kh = set_get_khash(mrb, self);
|
|
if (!kh) return mrb_false_value();
|
|
|
|
for (mrb_int i = 0; i < argc; i++) {
|
|
khiter_t k = kh_get(set, mrb, kh, argv[i]);
|
|
if (k == kh_end(kh)) {
|
|
return mrb_false_value();
|
|
}
|
|
}
|
|
|
|
return mrb_true_value();
|
|
}
|
|
|
|
/*
|
|
* call-seq:
|
|
* set.include_any?(*objects) -> true or false
|
|
*
|
|
* Returns true if the set contains any of the given objects.
|
|
*/
|
|
static mrb_value
|
|
set_include_any_p(mrb_state *mrb, mrb_value self)
|
|
{
|
|
const mrb_value *argv;
|
|
mrb_int argc;
|
|
|
|
mrb_get_args(mrb, "*", &argv, &argc);
|
|
khash_t(set) *kh = set_get_khash(mrb, self);
|
|
if (!kh) return mrb_false_value();
|
|
|
|
for (mrb_int i = 0; i < argc; i++) {
|
|
khiter_t k = kh_get(set, mrb, kh, argv[i]);
|
|
if (k != kh_end(kh)) {
|
|
return mrb_true_value();
|
|
}
|
|
}
|
|
|
|
return mrb_false_value();
|
|
}
|
|
|
|
/*
|
|
* call-seq:
|
|
* Set[*ary] -> new_set
|
|
*
|
|
* Creates a new set containing the given objects.
|
|
*/
|
|
static mrb_value
|
|
set_s_create(mrb_state *mrb, mrb_value klass)
|
|
{
|
|
const mrb_value *argv;
|
|
mrb_int argc;
|
|
|
|
mrb_get_args(mrb, "*", &argv, &argc);
|
|
|
|
/* Optimized direct creation */
|
|
mrb_value set = mrb_obj_new(mrb, mrb_class_ptr(klass), 0, NULL);
|
|
khash_t(set) *kh = set_get_khash(mrb, set);
|
|
|
|
for (mrb_int i = 0; i < argc; i++) {
|
|
kh_put(set, mrb, kh, argv[i]);
|
|
}
|
|
|
|
return set;
|
|
}
|
|
|
|
void
|
|
mrb_mruby_set_gem_init(mrb_state *mrb)
|
|
{
|
|
struct RClass *set;
|
|
|
|
set = mrb_define_class(mrb, "Set", mrb->object_class);
|
|
MRB_SET_INSTANCE_TT(set, MRB_TT_SET);
|
|
|
|
mrb_include_module(mrb, set, mrb_module_get(mrb, "Enumerable"));
|
|
|
|
mrb_define_class_method(mrb, set, "[]", set_s_create, MRB_ARGS_ANY());
|
|
|
|
mrb_define_private_method(mrb, set, "initialize_copy", set_init_copy, MRB_ARGS_REQ(1));
|
|
|
|
mrb_define_method(mrb, set, "size", set_size, MRB_ARGS_NONE());
|
|
mrb_define_method(mrb, set, "length", set_size, MRB_ARGS_NONE());
|
|
mrb_define_method(mrb, set, "empty?", set_empty_p, MRB_ARGS_NONE());
|
|
mrb_define_method(mrb, set, "clear", set_clear, MRB_ARGS_NONE());
|
|
mrb_define_method(mrb, set, "to_a", set_to_a, MRB_ARGS_NONE());
|
|
|
|
mrb_define_method(mrb, set, "include?", set_include_p, MRB_ARGS_REQ(1));
|
|
mrb_define_method(mrb, set, "member?", set_include_p, MRB_ARGS_REQ(1));
|
|
mrb_define_method(mrb, set, "===", set_include_p, MRB_ARGS_REQ(1));
|
|
|
|
mrb_define_method(mrb, set, "add", set_add, MRB_ARGS_REQ(1));
|
|
mrb_define_method(mrb, set, "<<", set_add, MRB_ARGS_REQ(1));
|
|
mrb_define_method(mrb, set, "add?", set_add_p, MRB_ARGS_REQ(1));
|
|
|
|
mrb_define_method(mrb, set, "delete", set_delete, MRB_ARGS_REQ(1));
|
|
mrb_define_method(mrb, set, "delete?", set_delete_p, MRB_ARGS_REQ(1));
|
|
|
|
mrb_define_method(mrb, set, "__init", set_init, MRB_ARGS_NONE());
|
|
mrb_define_method(mrb, set, "__merge", set_core_merge, MRB_ARGS_REQ(1));
|
|
mrb_define_method(mrb, set, "__subtract", set_core_subtract, MRB_ARGS_REQ(1));
|
|
|
|
mrb_define_method(mrb, set, "__union", set_core_union, MRB_ARGS_REQ(1));
|
|
|
|
mrb_define_method(mrb, set, "__difference", set_core_difference, MRB_ARGS_REQ(1));
|
|
|
|
mrb_define_method(mrb, set, "__intersection", set_core_intersection, MRB_ARGS_REQ(1));
|
|
mrb_define_method(mrb, set, "__xor", set_core_xor, MRB_ARGS_REQ(1));
|
|
|
|
mrb_define_method(mrb, set, "==", set_equal, MRB_ARGS_REQ(1));
|
|
mrb_define_method(mrb, set, "hash", set_hash_m, MRB_ARGS_NONE());
|
|
mrb_define_alias(mrb, set, "eql?", "==");
|
|
|
|
mrb_define_method(mrb, set, "join", set_join, MRB_ARGS_OPT(1));
|
|
mrb_define_method(mrb, set, "inspect", set_inspect, MRB_ARGS_NONE());
|
|
mrb_define_method(mrb, set, "to_s", set_inspect, MRB_ARGS_NONE());
|
|
|
|
mrb_define_method(mrb, set, "reset", set_reset, MRB_ARGS_NONE());
|
|
|
|
/* Bulk operation methods */
|
|
mrb_define_method(mrb, set, "add_all", set_add_all, MRB_ARGS_ANY());
|
|
mrb_define_method(mrb, set, "delete_all", set_delete_all, MRB_ARGS_ANY());
|
|
mrb_define_method(mrb, set, "include_all?", set_include_all_p, MRB_ARGS_ANY());
|
|
mrb_define_method(mrb, set, "include_any?", set_include_any_p, MRB_ARGS_ANY());
|
|
|
|
/* Register our new C implementations */
|
|
mrb_define_method(mrb, set, "superset?", set_superset_p, MRB_ARGS_REQ(1));
|
|
mrb_define_method(mrb, set, ">=", set_superset_p, MRB_ARGS_REQ(1));
|
|
mrb_define_method(mrb, set, "proper_superset?", set_proper_superset_p, MRB_ARGS_REQ(1));
|
|
mrb_define_method(mrb, set, ">", set_proper_superset_p, MRB_ARGS_REQ(1));
|
|
|
|
mrb_define_method(mrb, set, "subset?", set_subset_p, MRB_ARGS_REQ(1));
|
|
mrb_define_method(mrb, set, "<=", set_subset_p, MRB_ARGS_REQ(1));
|
|
mrb_define_method(mrb, set, "proper_subset?", set_proper_subset_p, MRB_ARGS_REQ(1));
|
|
mrb_define_method(mrb, set, "<", set_proper_subset_p, MRB_ARGS_REQ(1));
|
|
|
|
mrb_define_method(mrb, set, "intersect?", set_intersect_p, MRB_ARGS_REQ(1));
|
|
mrb_define_method(mrb, set, "disjoint?", set_disjoint_p, MRB_ARGS_REQ(1));
|
|
|
|
mrb_define_method(mrb, set, "<=>", set_cmp, MRB_ARGS_REQ(1));
|
|
|
|
mrb_define_method(mrb, set, "flatten", set_flatten, MRB_ARGS_NONE());
|
|
mrb_define_method(mrb, set, "flatten!", set_flatten_bang, MRB_ARGS_NONE());
|
|
}
|
|
|
|
void
|
|
mrb_mruby_set_gem_final(mrb_state *mrb)
|
|
{
|
|
}
|